evolutionleo / LDtkParser

A parser for .ldtk levels for GameMaker
https://discord.gg/bRpMgTquAr
MIT License
49 stars 2 forks source link

New feature : parsing neighbours ? #8

Open MichelVGameMaker opened 2 years ago

MichelVGameMaker commented 2 years ago

Hello ! Thank you for your parser !

I was wondering if you could add the level's neighbours features

1-Parsing the level, would update a map / struct registring all neighbouring levels (for north, south, west, east) 2-A function LDtkNeighbour(_room, _dir) would be available when user needs it to fetch the neighouring levels for the associated _room and in the _dir direction {n, s, w, e}. That way, user can call LDtkNeighbour(room, e) to get the room to the right.

Of course, I'll understand if you can't. Your parser is already very useful.

__neighbours | Array of Object | An array listing all other levels touching this one on the world map. Only relevant for world layouts where level spatial positioning is manual (ie. GridVania, Free). For Horizontal and Vertical layouts, this array is always empty.This object contains the following fields:

evolutionleo commented 2 years ago

hmm I have thought about this in the past, it's a really useful feature for metroidvania type of games, and it should be very much doable, especially with the __neighbours JSON field that I didn't know existed in the specs lol

one thing you need to consider however is that there could be more than one neighbour to a level on each side, e.x. what if on the eastern side of a bigger room there are 2 different hallways that lead to 2 smaller rooms (split horizontally), so the function LDtkNeighbour() should also take in as arguments the x and y position of where the transition is happening relative to the room

MichelVGameMaker commented 2 years ago

lol... yes I was aware of it, but...

I thought it would be good enough to have only one room per direction and that could be a constraint of the feature... (I am often happy to live with limitation that I have to follow as long as it allows to move on quickly with my project... but you are right).

When I thought about a proper way to manage transitionns, I only see a way with worldX, worldY... but it seems more complex: 1-Parsing would create an array of levels coordinates (right, top, left, bottom) for all levels, using worldX, worldY, pwWid, pxHei. 2-The function for room transition would be passed the targeted coordinates to find (against this array) in wich level you land...like point_in_rectangle LDTK_transition(_x, _y)... the function can be implemented in two ways: _x and _y can be in 1.room coordinates or 2.in world coordinates

The easier for the user is room coordinates. I could use this by placing "transition" objects just behind the borders of the room where passages are open to the player. Upon collision with player, the object would call LDTK_transition(player.x, player.y) to return a struct with three things 1.the new room, 2. the new x coordinates in the new room, 3.same for y...

So it means internaly LDTK_transition(_x, _y) needs to offset _x and _y with the room worldX and worldY... (retrieved from the array ?) and then compared against levels coordinates to find the new level, then substract worldX, worldY of the new level from _x, _y to get new _returned_x and _returned_y....

Hyomoto commented 2 years ago

I should probably do this as a pull request, but as my format for using LDtk is not the same I'm just gonna text dump. What I do is make a list of all the levels, then I do a second pass to check if they share an edge, if so add them to a neighbors array for that level. When the player tries to leave the area it tests if the new tile would be in one of the neighbors, and if so, passes that coordinate into the transition manager.

The point that I'm getting at is multiple edges is quite doable, but the major consideration is that the level data is absolute. In fact, the major issue with odd neighbors, it turns out, is actually the camera. If all the level share a similar edge, the transition is quite simple. If not, since the rest of the level data isn't loaded, you could end up with "voids" or camera snapping. Anyways, this is what it might look like.

static __neighbors__    = function( _level ) {
    var _l  = _level.x;
    var _t  = _level.y;
    var _r  = _l + _level.width;
    var _b  = _t + _level.height;
    var _list   = [];

    var _i = 0; repeat( array_length( levels )) {
        var _comp   = levels[ _i++ ];
        if ( _comp == _level )
            continue;

        if ( rectangle_in_rectangle( _l, _t, _r, _b,
            _comp.x, _comp.y, _comp.x + _comp.width, _comp.y + _comp.height ) > 0 )
            array_push( _list, _comp );

    }
    return _list;

}       
// neighbors
var _i = 0; repeat( array_length( levels )) {
    var _level  = levels[ _i++ ];
    _level.neighbors = __neighbors__( _level );

}

And of course, you need a function to look for a valid neighbor:

/// @param {Real} _x
/// @param {Real} _y
/// @returns {Struct.Level,Undefined}
/// @param  Returns a neighboring Level if it contains the given coordinates.
find_neighbor   = function( _x, _y ) {
    _x += level.x;
    _y += level.y;

    var _i = 0; repeat( array_length( level.neighbors )) {
        if ( level.neighbors[ _i++ ].contains( _x, _y ))
            return level.neighbors[ _i - 1 ];

    }
    return undefined;

}

Again, my format is different but I quite randomly ended up here and I'm posting this in case it ends up being slightly helpful.