1
\$\begingroup\$

Sorry if this is a long post, but I'd like to explain the context before asking my question ^^"

I'm currently working on my "first" game (the more advance one, at least). It'll be a 2D-ARPG where the player will be able to interact with the map, by doing things like chopping tree, farming, mining, etc...

As it is my first game, I didn't really know where to go so I started by implementing basics functionalities, and i added more while the game took shape.

With that in mind, the first thing I did is implementing a TileMapReader that take JSON files generated by Tiled, and render it on screen. I've kept the structure Tiled use (github.com/bjorn/tiled/wiki/JSON-Map-Format) in order to store the tiles : The Map have a list of Layer, and each Layer as a list of "gid", corresponding to a Tile in the Tile Set. I generate my map directly from the Tile Set, without creating a Tile Object ... So my map really is just a bunch of Integers, corresponding to a Tile id. I generate then the display by pasting the right piece of the Tile Set at the right place on the map.

The map in Tiled

After that, I start implementing a Camera that could be move around the Map, and then a Player character who can move around too. Currently, the player is an instance of a "Body" class that is store with a x;y location in a field on the Map Object, and is display on a specific empty Layer like any other Tiles (except the sprites come from other external pictures, and not the Tile Set). I linked the control and the Camera to the character, and here I go ! Walking-in-forest simulator 2016 !

screenshot of the game

So now, I want to start implementing interaction like collision and so. The problem is that I really can't decide how to do this. The two first thing I'd like to add to the game are interactions with the trees : collisions and chopping.

Since the beginning, trees are just collections of tiles on different Layers, like the rest of the map. But I think I have to make them as Object entity, just like my "Body" ...

To do so, I've thought of servals ways :

  • I could parse my Map Tile by Tile, and store my tree-tile's position in order to create my tree, but I find it way to difficult since I don't really know wish Tiles in the Tile Set are the tree-tiles, so I don't know theirs gid. Moreover, a tree is composed of servals Tiles, placed on servals Layers. So it'll be a pain to locate a single tree correctly on the map. Finally, the problems multiplies themselves if I add other entity like water or rocks ...
  • Another idea was to create another Map in which I store all the entity as Objects (and use the tile map for ground and water only). But if I do that, would the Tile Map really be useful ? wouldn't it be better to just randomly-generate a Map entity-full directly ? And since I want to add farming in the future, grass tiles would be passed on the Object-Map too, right ? So the Tile Map will become completely obsolete ...

I decided to go with the second one, but here again, I had to pick a way to store this second Map. I wanted to go with a Tree-like Graph in which I would separate different sections of the map (like each node is 1/2 of it's parent node, and the last Nodes are the different entity with their positions). That would help a lot for the collision's detection, but I think it still really complicate to implement ...

So here my questions : is my approach on this player-world interactions correct ? since it is my first "big" game, I really would like to know if I'm doing it right or not ... and if not, how can I handle this interactions properly ?

I'm posting these here, hopping that there is a simple answer to it (even "just do it" would do ^^ (even if some explanation would be really nice)) and that this post will not be be closed for being too broad ^^"

\$\endgroup\$
1
\$\begingroup\$

The method I would choose for this is to just use your existing map for as much as possible. This is essentially your first approach. Yes, you will need to figure out the IDs of trees (and other relevant objects), but on the plus side you don't need to make any changes to how you're creating maps, storing them, loading them, rendering them...

Regarding collision. You don't need to search for the trees and store them as different objects, necessarily. All you really care about is the locations of the tree base, the other tiles of the trees don't effect collision as you can walk behind them. You will need to encode this data somewhere that the id for tree base (or water, or rock) is a non-passable tile. Using your existing layers (it looks like multiple layers will have non-passable tiles), as your player is walking around you can iterate through the layers to check if they are about to walk into a non-passable tile and stop the movement. If you plan on using layers that you can both walk under and on (eg- bridges that you can across and under) then you will need to adjust which layers you're checking for collision.

int[] SolidTiles = { 1, 2, 5 }; // You may consider loading this from a data file.

// Determines whether the tile at (x,y) can be passed through.
bool IsTilePassable(int x, int y)
{
    for (int i = MinLayer; i <= MaxLayer; i++)
    {
        int tile = Map.GetLayer(i).GetTileAt(x, y);
        if (SolidTiles.Contains(tile))
        {
            return false;
        }
    }
    return true;
}

Regarding interaction. Let's extend this approach to accommodate interactions with the tiles. Instead of using some array of IDs, you can create some objects that contain as much information as you want. Then you just need to query these objects to find out about them. You only need to create as many info objects as you have different types of tiles. 100 trees on your map? No need to create 100 objects to track them, they're all the same type of object, just use a single info object which contains information common to them all (heck, maybe even different kinds of trees can use the same shared info object). As these objects hold only common shared information, note that they do not contain information like the tile's position. The map already knows where the tiles are.

class TileInfo
{
    // Returns whether a tile can be passed through.
    bool IsSolid();

    // Returns an object describing the interaction possible with this tile.
    InteractionInfo GetInteraction();
}

Now your collision method can look something like this:

// Determines whether the tile at (x,y) can be passed through.
bool IsTilePassable(int x, int y)
{
    for (int i = MinLayer; i <= MaxLayer; i++)
    {
        int tileId = Map.GetLayer(i).GetTileAt(x, y);
        // This TileInfoFactory is essentially a Dictionary of int -> TileInfo.
        // At some point earlier you would initialize these objects, possibly
        // loading them from a file.
        // It may be useful to create a default TileInfo which it can fall back on when an 
        // ID is not defined. This will allow you to skip writing definitions for
        // some tile types and only write definitions for ones that actually have an impact
        // (eg- trees, rocks).
        TileInfo tile = TileInfoFactory.GetTileInfo(tileId);
        if (tile.IsSolid())
        {
            return false;
        }
    }
    return true;
}

Now interacting with tiles is basically the same logic as colliding with them:

// Interacts with the tile located at (x, y)
void Interact(int x, int y)
{
    for (int i = MinLayer; i <= MaxLayer; i++)
    {
        int tileId = Map.GetLayer(i).GetTileAt(x, y);
        TileInfo tile = TileInfoFactory.GetTileInfo(tileId);
        InteractionInfo interaction = tile.GetInteraction();
        if (null != interaction)
        {
            // You will need to decide how your engine performs an interaction.
            interaction.Execute();
            return;
        }
    }
}

The interaction info object can contain information like what kind of player animation to show (eg- chopping) as well as how it effects the world state. For instance, once you interact with a tree, it may be chopped down and thus not on the map anymore, so you need to update the map.

class InteractionInfo
{
    List<MapChange> _mapChanges;

    // Called when the player has finished interacting with the tile at (x, y)
    void Finished(int x, int y)
    {
        foreach (MapChange change in _mapChanges)
        {
            change.Apply(x, y);
        }
    }
}

class MapChange
{
    int _layer;
    int _offsetX;
    int _offsetY;
    int _newTileId;

    void Apply(int x, int y)
    {
        Map.GetLayer(_layer).SetTileAt(x + _offsetX, y + _offsetY, _newTileId);
    }
}

Finally, I assume you will want to save your map between play sessions. Simply serialize it back into JSON and save it on disk. I recommend using a different filename than what the base state is saved as so you can start your game over. When you load the game, you can first check if, say, "map-saveslot1.json" exists before defaulting to the base "map.json"

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.