AdamsLair / duality

a 2D Game Development Framework
https://adamslair.github.io/duality
MIT License
1.41k stars 290 forks source link

Create a Tilemap Plugin #249

Closed ilexp closed 8 years ago

ilexp commented 8 years ago

Tilemaps are an incredibly easy way to create game worlds in 2D games, used in platformers, topdown games, strategy games, etc., so it might be worth considering to create an "official" tilemap implementation - especially since it has often been asked how to do this.

Some early (personal) notes on the topic as a reminder for myself: notes

Sorry for the bad image quality - I will transcribe the most vital information into (generally) readable text when requested or while iterating on it.

ilexp commented 8 years ago

Progress:

ilexp commented 8 years ago

Thoughts on Tilesets

A Tileset defines a set of tiles. (Well, obviously.) This does not include any actual visuals / pixel data. All pixel data is stored in distinct Pixmap resources, which are referred to by the Tileset.

Visual Layers

A single tile can have multiple visual layers, which can be used by the renderer later as needed. Examples are {diffuse, normal, specular}, {regular, snowy, wet} or similar. The tileset doesn't care or define what to do with each layer. There is one Pixmap for each visual layer, not for each tile. A tile's visual representation always consists of the same combination of visual layer parts - think of them as the different textures in a tileset material, although their actual usage will be entirely up to the renderer.

A visual layer is configurable using the following data:

Metadata

A tileset also defines metadata about each tile. Certain kinds of metadata are fixed an required by the system, but it is important to allow users to specify their own metadata as well - for game-related code and general customization. In general, tile meta information should be stored as a struct array to streamline access and storage.

System metadata (see below for more info):

Custom metadata can be added to each tile as a tag. The actual tags should be stored in a distinct array, whereas each tile just contains an index to the tag array. This allows users to easily modify tags centrally and is potentially more GC friendly as a bonus.

Collision Data

There are various ways to define collision data and integrate collision detection with Duality. The easiest way for integration would be to define collision data in the Tileset generate RigidBody shapes from the actual tilemap. This allows for physical interaction without specialized code. How should this collision data look like?

Primitive / Boolean

Each tile is either passable or not. This approach is insufficient for more complex tiles.

Four-Direction Passability

Each tile defines passability for all of its four edges separately. This allows "corridor" tiles that allow to move in one direction, but block paths in another. It's also what RpgMaker does, to name one example. This approach is a step in the right direction, as it allows more flexibility.

Corner-Fill Solidify

Each tile defines whether each of its four corners is solid or not, as well as a general "fill" flag on whether the specified shape is filled or just a set of edges.

It also allows a limited degree of collision shape interaction between tiles when their corners overlap, resulting in two adjacent near-impassable tiles joining together to form one solid impassable one.

This approach is likely to be preferred.

Vertex-Fill-Solidify

Building up on the previous approach, more vertices could be added to allow more sophisticated slopes and passability. However, this is probably too much.

When more complex collision shapes are required, users can always tag their tiles and write a custom Component that expresses these complex shapes.

AutoTiles

The plugin should use the Extended AutoTile pattern for user convenience. Internally, each AutoTile (3x4) should be expanded into 47 distinct tiles for quick and generalized access. This affects both visual layers and collision information and can be done as an OnLoad step.

The original AutoTile block no longer needs to be included explicitly in the final expanded tileset.

As it becomes apparent when thinking about the implication of this expansion and editor support, tilesets need to think about two kinds of tiles: Conceptual tiles and actual tiles. While all permutations of an AutoTile are each an actual tile (as stored in the tilemap), they are all the same conceptual tile (as seen by the editor for user experience).

Serialization

With tilemap support in mind, it becomes even more important that the serialization of struct arrays is as efficient as possible. Check this.

ilexp commented 8 years ago

Progress:

ilexp commented 8 years ago

Note: A tilemap should be configurable as to whether it should apply Z offsets (actual Z or vertex offset) to each subsequent row of tiles, and how exactly to apply them. This is required for perspectives like the one seen in typical retro RPG games, where characters and mapped objects can span multiple tiles and need to be z-sorted correctly.

Retro Rpg Sample

See the characters being behind the palm tree - if they walked around it, they should properly appear in front of it.

There could be different modes of Z offsets, and individual tiles could allow specifying additional Z offset data, for example to adjust the Z layer of higher palm-tree tiles, so the renderer can match their Z offset with the root's and they appear on a single Z layer. How "high above the ground" a tile begins is an information that would be defined in the tileset as well, and not primarily the tilemap - although it should be considered to allow tilemaps to specify this information as well, so the same wall-tile can be "stacked" Z-correctly.

ilexp commented 8 years ago

Note: There probably should be some way to handle heights in a non-hacky way. See this for reference, especially the sideways stairs. All of this can certainly be done with some scripting / hacking power, but if there is a clean, generalized way to handle this, it could be part of the equation. Reconsider this later.

ilexp commented 8 years ago

Dialog with one of the Crosscode guys about their tilemap implementation, to get some inspiration on how to handle heights:

crosscode_tilemaps

The above approach of allowing to specify height offsets per tileset item and per tile in a tilemap, as well as a flat / upright distinction should be enough to allow tilemap renderers to handle this with proper per-vertex Z offsets.

hsnabn commented 8 years ago

For collisions, you could include the primitive (boolean) collision by default, and allow users to choose any other included types as well.

Some tiles wouldn't need any other sort of collision detection logic, such as completely impassable tiles (chasms etc).

ilexp commented 8 years ago

For collisions, you could include the primitive (boolean) collision by default, and allow users to choose any other included types as well.

Some tiles wouldn't need any other sort of collision detection logic, such as completely impassable tiles (chasms etc).

Yep, certainly true!

The current plan of using the "Corner-Fill Solidify" approach would still allow boolean passability by simply specifying a tile to be completely solid, so this is essentially a matter of how comfortable the editor for this will be.

Since I plan a distinct Tileset / Tilemap editor, rather than relying only on the default Object Inspector editing, this shouldn't be a big problem. I'll have the workflow in mind and will try to not force users to use a more complex approach than they require.

ilexp commented 8 years ago

Progress:

ilexp commented 8 years ago

Testing Resources

Tiles with spacing: testtiles AutoTile Template: testautotile All 47 permutations of an expanded AutoTile: expandedautotile

ilexp commented 8 years ago

Okay, so this issue has been mostly a random stream of thoughts, ideas and concepts. This needs to change, as it already becomes apparent that this will be a bigger task to do. Progress posts should use an "immediate todo" footer, so it's always clear what's next on the list and a general overview remains.

In order to generate a feedback loop during development, the first goal should be to create an operational implementation of Tilesets, Tilemaps and TilemapRenderers. This will enable test code to set up Resources and GameObjects that are directly visible in the editor and testbed. From there, new features can be added continuously.

Immediate ToDo:

ilexp commented 8 years ago

Progress:

Immediate ToDo:

Tilemap API

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Public Domain / CC0 testing tileset with actual content: testtiles Taken from here.

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

For reference on the Z sorting / height / depth handling of tilemaps, this is how RMXP handles it:

rpgmakerxp_zsort

Basically the same as the above mentioned "height offset" idea for tilesets.

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

This plugin being an optional sidetrack of Duality development, its workload should be reduced to an absolute minimum until its first fully functional release in order to shift towards parallel development. Maintaining an extra branch makes it harder to introduce bigger changes into the framework on a non-master branch.

Things that need to be done before release:

Things that are optional and can be left out for now:

ilexp commented 8 years ago

Progress:

Roadmap:

ilexp commented 8 years ago

This issue has been split up into multiple issues to keep track of progress and concept, see the referencing issues above.