Metroid-vania style level design

Recommended Posts

Hi all,

I'm currently designing a map of rooms for a Metroid-style game (really inspired by the AM2R remake) and wanted to run my design by you for some feedback.

My thinking is you have a Room class, said class being able to load a text file which is made up of a map of ID's. These ID's would be mapped to different block types which have varying properties (wall/floor/lava blocks as an example). Each room would contain one+ door objects. These doors would have ID's allowing you to link rooms based on the connecting ID's. This would also be information found in each rooms textfile config.

Every room would contain an inventory of objects like enemies, items, power-ups, that the player can interact with in some way. There will be no random generation (yet, limited experience) and each room will be 'crafted' in a text file. The locations for these item would also be laid out in the text file that is loaded. Once loaded, their individual behaviours would be (like enemies patrol etc.).

My thinking is also to load rooms on a basis relative to the current room the player is in. The rooms connecting to the current room are loaded into memory and once a new room is entered, rooms outside of the depth n-1 or n+1 will be unloaded.

Thanks for your time guys!

Stitchs.

Share this post


Link to post
Share on other sites

That seems like a nice first iteration. However, a lot more goes into a good Metroid style map, like walls that disappear when you walk through them, that might not fit into a nice clean C++ class like how you'd like. Also, while there are some occasional warp points in Metroid maps, most of the actual world is laid out on a grid, so specifying the room IDs on every door may be a bit tedious (and redundant: every door has a dual on the other side of the wall, so data is duplicated for every door). You might want to rethink that at a later date.

Another thing you'll have to figure out is how to manage changing geometry (e.g. lava disappearing after Metroid are killed in AM2R) with your map or grid of tiles idea. Also, with collectable items you have to also be looking at your current inventory and save files so you can't collect multiple of the same item, which is another thing that might not fit into a nice little class.

You're mind is in the right place and it's as good a starting point as any, but be prepared and willing to have to rework the design in the future if you need to. Metroid maps are dynamic with lots of changing parts. That's part of what makes their world immersive. However, things like that are usually kind of messy to make in practice, but that's okay.

Share this post


Link to post
Share on other sites

Thanks for the reply. I did wonder if what I had would be expandable for more complex features and interactions.

13 minutes ago, CulDeVu said:

Also, while there are some occasional warp points in Metroid maps, most of the actual world is laid out on a grid, so specifying the room IDs on every door may be a bit tedious (and redundant: every door has a dual on the other side of the wall, so data is duplicated for every door). You might want to rethink that at a later date.

So when you say that the world is laid out on a grid, does this mean that all the rooms are loaded into memory from the very start of the game, with resources such as sprites and graphics being loaded on  "is in room" basis? Does the concept of rooms even exist? Or is everything traversable as if the entire map is a single room/level?

Stitchs.

Share this post


Link to post
Share on other sites
Posted (edited)
1 hour ago, stitchs_login said:

So when you say that the world is laid out on a grid, does this mean that all the rooms are loaded into memory from the very start of the game, with resources such as sprites and graphics being loaded on  "is in room" basis? Does the concept of rooms even exist? Or is everything traversable as if the entire map is a single room/level?

No, not at all. Take for example this portion of the AM2R map (spoilers I think):

maxresdefault.jpg

Notice that all the doors between "rooms" are located along the green grid lines behind the map, and so they only go left, right, up or down into the neighboring rooms in those directions. The only exception in AM2R that I can remember is the warp rooms. IIRC, you can even Space Jump up the elevator shaft to the wrecked spaceship if you want!

The idea I was trying to suggest is that, if you know what grid cell the door is in and the direction it's facing, then you could search for the adjacent room that it's connected to if you have some list of rooms somewhere laid out like that. This would give you Metroid-style maps without having to record the door ID for every single door in the entire world.

 

Edit: this also works perfectly well for ways to traverse rooms other than doors, like bombing the floor causing you to fall through into the room below.

Edited by CulDeVu

Share this post


Link to post
Share on other sites
Posted (edited)

As it happens, I'm developing a Metroid-style game called Hexoshi. It's also libre, so source code is available if you want to study or fork it:

http://hexoshi.nongnu.org

The way I implemented the map system was to have a JSON file listing the location of every room on the map, a JSON file listing the "map objects" (walls, doors, powerups, etc) at every location on the map, and a JSON file storing metadata. All of these files are generated automatically before the game is shipped out, using the function "generate_map" (activated by the -m option from the command-line when starting the game), which examines each room for doors to connect rooms together, as well as any interesting objects (such as powerups and save points). All doors simply indicate what room they go to; each room is a TMX file with an arbitrary file name (with the exception of the first room, sort of the origin room, which is "0.tmx"). Finally, to make non-rectangular rooms possible, it is possible to mark areas with "ignore region" zones (where nothing is inserted), and it is also possible to explicitly mark locations for map walls and areas where the view will be locked to a particular position either horizontally or vertically.

When the game runs, all this information is used to draw the map with the draw_map function. To facilitate removal of map elements, a list of "removed" map elements is kept; any object on this list is excluded when the map is drawn. Areas which are "unknown" are also excluded.

The end result of the map looks like this, currently (noting of course that the game is incomplete and intended to be several times larger than this in the end):

map.png.d7980a438f1352543e4e0e483c6a86d0.png

Hope that helps. :)

7 hours ago, CulDeVu said:

most of the actual world is laid out on a grid, so specifying the room IDs on every door may be a bit tedious (and redundant: every door has a dual on the other side of the wall, so data is duplicated for every door).

It's really not that tedious. Attempting to store the rooms rigidly in a grid can work, but I think that method is a little less reliable, and you still have to place the doors manually anyway; in Tiled, going to the door object's properties and writing in the file name of the room it leads to takes 10 seconds at most (for typing both of them). There's also a benefit: if, suppose, you want to resize a room somewhere and shift everything connected to it to compensate (rather than having to shorten something else), or if for some other reason you want to shift the position of half the map, it's a lot simpler if the connections are based on explicit definitions than if you're relying on grid positions. In the latter case, you have to manually edit some kind of data file to change every room's position. In the former case, you just do the resizing(s) necessary, and everything else is automatic.

Edited by JulieMaru-chan

Share this post


Link to post
Share on other sites

Thanks for the responses. Alot more to think about that I first anticipated.

11 hours ago, CulDeVu said:

The idea I was trying to suggest is that, if you know what grid cell the door is in and the direction it's facing, then you could search for the adjacent room that it's connected to if you have some list of rooms somewhere laid out like that. This would give you Metroid-style maps without having to record the door ID for every single door in the entire world.

With this approach, couldn't it become inefficient to have to search the possible rooms each time, if the map is huge? What I'm saying is, that you could do an initial cull on rooms that are past the door (say, the door is facing left, and if the right half of the grid, then we ignore all rooms behind it), but you would still have to search every rooms grid coordinates and compare 2 values. Whereas with an ID to ID mapping, you either compare one value, or create some Door class, which stores the ID of the door it joins to (similar to how a linked list stores a reference/pointer (language dependent =D) to the next element in a list.

5 hours ago, JulieMaru-chan said:

The way I implemented the map system was to have a JSON file listing the location of every room on the map, a JSON file listing the "map objects" (walls, doors, powerups, etc) at every location on the map, and a JSON file storing metadata. All of these files are generated automatically before the game is shipped out, using the function "generate_map" (activated by the -m option from the command-line when starting the game), which examines each room for doors to connect rooms together, as well as any interesting objects (such as powerups and save points).

This does sound pretty interesting, and I do a lot with JSON at work, so it wouldn't be unfamiliar ground for me. Could you provide snippets of what info these files contain. When you say "generate", do you have a randomly generated labyrinth every time a brand new game a started?

Thanks again. Really useful stuff!

Liyaan.

Share this post


Link to post
Share on other sites
49 minutes ago, stitchs_login said:

With this approach, couldn't it become inefficient to have to search the possible rooms each time, if the map is huge?

If so, you could pre-process it and store "rooms linking to this room" in each room, making it just a simple look-up.

Share this post


Link to post
Share on other sites
Posted (edited)
4 hours ago, stitchs_login said:

This does sound pretty interesting, and I do a lot with JSON at work, so it wouldn't be unfamiliar ground for me. Could you provide snippets of what info these files contain.

As I said, Hexoshi is libre, so you can actually study the entire source code. You could even use my code as long as you're fine with the GNU GPL. The repository is here:

https://savannah.nongnu.org/git/?group=hexoshi

More specifically, this is the Python source code file (yeah, it's only one file; I should change that, but I consider it a low priority):

http://git.savannah.nongnu.org/cgit/hexoshi.git/tree/hexoshi.py

4 hours ago, stitchs_login said:

When you say "generate", do you have a randomly generated labyrinth every time a brand new game a started?

No, I use Tiled (http://mapeditor.org) to draw out the rooms. What's generated is the files which indicate the map; this is because the map needs to be a grid to be efficient. The generation, as I say, is in the "generate_map" function; it basically just starts at "0.tmx" (the first room), then recursively searches through all rooms it is connected to via doors. For each room, particular objects are looked for, and each grid segment of the map is designated based on this, then saved to the aforementioned JSON files so that the process (which is slow) doesn't have to happen each time the game starts up.

The JSON files which are automatically generated look like this (just showing a few entries in each):

{
    "0.tmx": [
        0, 
        0
    ], 
    "1.tmx": [
        -3, 
        1
    ], 
    "10.tmx": [
        -7, 
        1
    ]
}

Those are just the coordnates of each room within the map.

{
    "-1,1": [
        "door_right", 
        "wall_top", 
        "wall_bottom"
    ], 
    "-1,10": [
        "wall_left", 
        "wall_right"
    ], 
    "-1,11": [
        "wall_bottom", 
        "wall_right"
    ]
}

This is a list of coordinate pairs with the list of "map objects" found in those coordinates.

So quite simply, the first file is used to determine where you are on the map, while the second file is used to determine what the map looks like. The first one also is used when revealing segments of the map with the "map disk" (similar to the map terminal things you find in Super Metroid).

Of course, the full details are in Hexoshi's source code.

Edited by JulieMaru-chan

Share this post


Link to post
Share on other sites
Posted (edited)

It's not ready for a release right now. I'm going to force one out next month or the month after, though. In the meantime, there's a video I recorded last month, and this should be plenty to demonstrate the mapping system for the OP:

https://goblinrefuge.com/mediagoblin/u/onpon4/m/hexoshi-2017-07-19/

If you really want to play it now, make sure you have all the dependencies installed: pathlib, six, sge-pygame, tmx, xsge_gui, xsge_lighting, xsge_path, xsge_physics, xsge_tmx; and of course, Python itself.

Edited by JulieMaru-chan

Share this post


Link to post
Share on other sites

I watched a few minutes of your video, some pretty slick looking stuff there! ;)

Far beyond what I was even imagining my room/grid layout would look like!

So what you're saying is, you have your tmx files, and the generation process looks through these and outputs JSON, which is read later to layout the connection of the rooms? And I assume the tmx extension is from the Tiled map program you use?

Stitchs.

Share this post


Link to post
Share on other sites
1 hour ago, stitchs_login said:

So what you're saying is, you have your tmx files, and the generation process looks through these and outputs JSON, which is read later to layout the connection of the rooms? And I assume the tmx extension is from the Tiled map program you use?

Yes and yes. :)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now