Platforming physics


Day 25-29

English isn't my first language, so I might write some broken English.

Finally!    I got the platforming physics working!

Thanks to this article, I got the idea of how to approach the tilemap collision detection in Butano.

Let's dive into this!

(Added on 2022.08.22.  Please note that this method only works for affine background.
There's a way to do the similar thing with regular background too, it is discussed on the end of this article.)


Butano engine manages 8x8 tiles within bn::affine_bg_ptr by giving an unique integer ID number per unique tile.

This unique ID starts from 1, and is given from the top-left-most tile to the bottom-right-most tile.

(Except for a full-transparent tile, which has the ID of 0.)


e.g. If I set up the affine background image like this...



The unique ID is automatically set up like this.    (transparent tile is id 0, omitted)



...and there is a way to get the unique ID of the tile at the specific tile position!

Say, I want to get the ID of (x, y) = (3, 7) in tile coordinate system shown below:


... The ID is 18 in this case.

I wrote a working demo to show how to get this id.

1
2
3
4
5
6
7
8
9
10
11
12
bn::affine_bg_ptr bg = bn::affine_bg_items::image_file.create_bg(00);
// bn::affine_bg_map_cell is the unique id number,
// which is just a type alias of uint8_t.
bn::span<const bn::affine_bg_map_cell> cells = bg.map().cells_ref().value();
// You can get unique id by indexing `cells` like this:
int x = 3, y = 7;
// COLUMNS: how many tiles in a row?
constexpr int COLUMNS = bn::affine_bg_items::image_file.map_item().dimensions().width();
// Get the unique id
auto unique_id_of_x_by_y = cells[x + y * COLUMNS];
 
BN_LOG(bn::format<32>("({},{}) is {}", x, y, unique_id_of_x_by_y));
cs


Great, so how do I do a collision detection with this?

I need to identify if this tile is a FLOOR, if that one is a CEILING, if other one is a WALL...


Thinking that each unique ID represents each unique Tile, I can use this ID as an index, and create a LUT of this tile type Flags!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class TileInfo
{
public:
    // Flags can have multiple properties
    // by combining enum constants with operator|
    enum class Flags
    {
        EMPTY = 0,
        CEILING = 0x01,
        FLOOR = 0x02,
        LEFT_BLOCKING_WALL = 0x04,
        RIGHT_BLOCKING_WALL = 0x08,
        SPIKE = 0x10,
    };

    Flags GetTileFlagsByPosition(bn::fixed_point position) const;
 
private:
    static constexpr int TILE_VARIATION_COUNT = 32;
    static constexpr int TILE_GROUP_COUNT = 9;
 
    const bn::affine_bg_ptr* bg_;
    bn::vector<Flags, TILE_VARIATION_COUNT> tileFlagsLUT_;
    // ...
};
 
inline TileInfo::Flags operator|(TileInfo::Flags f1, TileInfo::Flags f2)
{
    return static_cast<TileInfo::Flags>(static_cast<int>(f1) | static_cast<int>(f2));
}
 
inline TileInfo::Flags operator&(TileInfo::Flags f1, TileInfo::Flags f2)
{
    return static_cast<TileInfo::Flags>(static_cast<int>(f1) & static_cast<int>(f2));
}
 
inline bool operator!(TileInfo::Flags flags)
{
    return flags == TileInfo::Flags::EMPTY;
}
cs

...and initialize the tileFlagsLUT_ by reading the affine background like this:

(I'm using the id=0 empty tile as a delimiter.)

So that I can lookup tileFlagsLUT_[unique_id] and get the info that if it is a wall, ceiling, etc.


With some helper functions to convert bn::fixed position to tile coordinate index...

I can read the 4 edges of the player bn::fixed_rect collider and detect the collision!


The circles are the 4 edges of the player collider, and the big rectangle which has been cut off is the platform.

(e.g. collider 2 should be pushed back to upside, and collider 3 should be pushed back to left-side.)


After writing down part of the Truth Table, I found that it would be easier to check collisions in this order...


FLOOR -> WALL -> CEILING

... and deal with some corner cases where colliders are approaching to the corner of the platform ( no pun intended ;) )

I'm comparing x and y position of the one edge of the collider, relative to the corner tile.

And since every tile is 8x8, simple mod(x, 8), mod(y, 8) will do the trick...

Although Butano doesn't have built-in operator%(bn::fixed, bn::fixed), so you should implement one yourself.

(actually standard C++ neither does have one for floating point numbers...)

(Plus, when implementing this, you'll probably need negative integer division, which is one thing I hate in C++.)


and BOOM!  we have the collision resolved.



I've also added some player animations (although garbage)

The spike doesn't work, and no sound effects for now, but it will be implemented soon.


GitHub tag : Day-29

Download .gba file

Select : Enable/Disable debug view

Arrow : Move

: jump

L : use Left hand / R : use Right hand


===== Added on 2022. 08. 22. =====

Now that Butano added bn::regular_bg_map_cell_info helper class, you can do a similar thing with regular background too.

While the bn::regular_bg_map_cell is an unique tile id of the regular bg, it is not directly a tile index.
This is because flipped tile shares the same tile index.
So, you need to convert it to the tile index by using that helper class.

But, since the flipped tiles share the same tile index, you can't distinguish a ceiling with a floor, a left-wall with a right-wall, for my bg image.
You need to come up with your own workaround, considering the flipped tiles too.
See the docs page of bn::regular_bg_map_cell_info to how to work with it.

I have another directory for the same working demo.

Please see the example named dynamic_regular_bg from Butano source, too.

Get Symbol★Merged

Leave a comment

Log in with itch.io to leave a comment.