How to have entities interact through a tilemap

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By zephy

My entity node structure is:

enter image description here

My game is 2D, topdown, grid based and my goal is to handle collisions using the ID’s of the entity instead of having a ton of area2d’s and what not.


If the bear and zombie meet on adjacent tiles the attack animation will start.

:bust_in_silhouette: Reply From: Inces
  1. You need to have a dictionary of tile position : tile content for every tile.
var tiledata = {Vector2(0,0) : -1, Vector2(0,1) : empty,............. }
  1. You need to code a method to recognize a tile during interaction
    for example
    targettile = startingtile + movement direction
    or check surrounding tiles within a square.
  2. You need to check the contents of targettile in relation to content of startingtile and design IF statement for corrent interaction, for example :
if tiledata[startingtile] == 1 and tiledata[targettile] == 2 :
     animation.play() 

important thing is to design when grid is supposed to check for interaction. Obviously it cannot happen in process(). There must be a queue, an order to grid events, for example : whenever button is pressed grid checks if there will be an interaction with a wall, and only if there is no wall grid changes, and checks for surrounding tiles for additional interaction, and turn ends.
If You need a method to check tiles around a square range, just search it on this site, I have been answering this question a lot lately

Hello Inces! Thank you so much for helping with my project so far <3 it really means a lot! I forgot to mention that the entities in my world are not bound by player input, instead they change their positions by checking if there is an empty tile in their 4 direction vision and changing direction based on that. Would I have to loop over the tiledata dict to update their positions?

zephy | 2022-06-17 22:35

I should have known it was the same project, I was like “why is everyone using the name entity ?” :stuck_out_tongue:

So your entity doesn’t react to input, but YOu still need to abide to the rule of keeping queue/order of grid events.
Are they moving constantly in process() ? Are they constantly checking 4 directions in process() ? I faintly remember I did show You setget method earlier, so they can only check once whenever they approach a new tile.

Now You need to design a way they will check for another collisions with other moving entitites only once. I think this should no longer be a method for individual entities. Because when every entity approaches new tile in different timing than the other, recognizing collisions will be a total mess. You need to either make entities move perfectly simultanously, or force the gridmap to check all possible collisions every few moments.

And yes, You would have to loop through tiledata to update their positions.

Inces | 2022-06-18 07:36

Do you have any useful resources for a queue system thats related to my game? Also is it possible to give these entities some sort of ID like with tilesets?

zephy | 2022-06-18 11:27

Hey Inces, do you think it would be a better idea to create separate scenes for tiles instead of using the tilemap?

zephy | 2022-07-12 03:09

No, I think it would be highly inefficient :slight_smile:
However tilemap does have some serious drawbacks. I believe the only huge problem is applying shaders to tiles. If You want to have multiple visual effects for separate tiles on various interactions, then You might want to create custom tilemap using multimesh instances as children. Other than that it is better to use built_in tilemap

Inces | 2022-07-12 13:31

Hey sorry for the late reply, I was super busy this week. I figured it would be a little cumbersome. But I won’t lie I’ve been stuck on this for about a month now and I think I’ve made some progress in the way the data should be structured:

Is this good enough or are there flaws in this approach? Thank you again! Also, there is a tile called “empty” which is an actual tile that I gave to the tileset which is equal to an id of 28 instead of -1 for empty tiles by default.



zephy | 2022-07-17 03:03

ahm, it looks overengineered :stuck_out_tongue:

What do You need such complicated structure for ?
One dictionary should be enough to keep every stable data for your tiles. You can keep it in autoload and read data from it. You can also make such data in Google Sheet and save it as JSON file, if it is easier.
I don’t know why would You need these subroutine functions, that only return pretty much the same info they intake with arguments. I suspect such structure will require tedious coding later

Inces | 2022-07-17 14:50

I totally agree that this might be a little too much. I don’t think I mentioned this and this is also why I’m a little confused about using your method, I want to be able to generate a random world, and setting each position with its corresponding id seems very tedious. On top of that the world generation is like so:

Is it still possible to use 1 dictionary for this? Also, some tiles can change at runtime.

zephy | 2022-07-17 23:35

You need one dictionary for all stable data, and one for changable data.
All the information, that You designed to be always the same should be kept in one data structure. Like properties of a tiles

{ "grass" : {"walkable" : true, "index" : 1, "maxdurability" : 5, "Sprite" : "res/tiles/grass"},
"dirt" : { and so on }

This dictionary should be kept in Autoload script, because a lot of nodes will want to read this data, like your game menager node and tooltips.

Second dictionary, for changable data, can be much simpler, and only contain current properties regarding to tile position :

{ Vector2(0,0) : {"tile" : "grass", "durability" : 3}, Vector2(1,0) : { so on }}

So when generating a random layout You only iterate through currentdata dictionary to randomize tile types ( “grass” “dirt” “water” ), and when it is done, You set changable tiles properties to default values using stabledata dictionary

Inces | 2022-07-18 15:36

Hey, do you have discord or something? Can we message on there if that’s fine with you since Im its pretty hard to message each other like this.

zephy | 2022-07-19 14:34

I don’t have a discord and I only have time to answer here and there occasionally, sorry. I can see your question is more like a wide general problem, it is not something that can be answered once and for all, unforutnately I can’t become your project menager :slight_smile:

Inces | 2022-07-19 17:05

No thats understandable, and I really appreciate the help so far! But its just like you said, Im just having a hard time fully understanding this specific problem. I hope you dont mind is all. And I dont want you to feel that Im asking for everything, its just that this problem doesnt seem to have a lot of resources for godot at least. But like I said… Thank you so far!

zephy | 2022-07-19 23:49

So I created two dictionaries, one autoload and one inside my tilemap script. And just to test it out I did this:

And I still dont know if im using this autoload correctly

enter image description here





Since my textures are a tileset how would I get the sprite from it? Would I have to get the region and texture?

zephy | 2022-07-20 01:27

It is kinda what I had in mind :slight_smile:
My entries were examples though, You dont need sprites if You use tilemap, You can use tile index instead. You don’t even need to use string names, You can use tile index as a key data in stable dictionary. Still I would stick to names, because it is easier to understand.

I don’t quite get why is tile_ids structured like this. Is it supposed to be base of tiles, that are used for random creation ? You don’t need dictionary for this, array is enough, like

["dirt","natural_level_1","grass"]

Look, You can even add some randomrollchance key in your stable data dictionary, and it will define an individual chance for each tile type to be randomized on game start. With this You can iterate your stable data dictionary on world creation instead of creating another chunk of data elsewhere.

Also I noticed, that You are willing to add some NPCs or monsters as a part of tilemap. Now I understand what You asked before. I believe any “living” tiles with some kind of behavior should be Sprites childed to tilemap. Otherwise You will have to abide to very strict movement rules, that will be similar to some kind of wooden puzzle riddles. I don’t think You want to swap your entities with other tiles every time a movement occurs, I remember You wanted free movement or even fluid. Or how do You imagine it ?

Inces | 2022-07-20 19:44

Got it, just changed that to an array. I will add a rarity key as well. As for the entities, they spawn from breaking one of the tiles for example if I broke a "natural_level_1" tile, I would set the tile from "natural_level_1" to a "walkable_tile" and spawn the appropriate entity at the position of "walkable_tile". The movement is going to be a pixel-by-tile style movement. I will have an array of available directions that the entity can move and choose one direction from the array. Before it moves, ill have it check if there is already an entity there and if there isn’t then it moves but otherwise, it starts the attack sequence. The question is though, is a process method even necessary? This seems to be a good solution as well: Update Method · Sequencing Patterns · Game Programming Patterns

zephy | 2022-07-21 14:52

I cac’t open that page, but I agree this shouldn’t be updated in process. As I mentioned earlier, there should be strict timing of updating tilemaps as a whole, either by command chain or by timer ticks. But not every frame :slight_smile:

If You make entites as tiles, they will only be able to teleport as snapped to tilemap grid size. So You can use tiles to spawn them and detect their position/surroundings, but actual entities should be separate objects with sprites. This will allow You to interpolate their movement, for example by using tweens.

However it is still a good idea to keep track of entities and their positions in another changable data dictionary. I mean You could use just one dict for everything, but are You sure there will be only one tile/object at one position ? Or there can be both “walkable_tile” and “entity” in the same spot ?

Inces | 2022-07-21 15:07

I’m sorry, I put a period at the end of the link, it should work now. But it is possible for there to be 2 entities on the same tile.

zephy | 2022-07-21 15:22

Ok but too long :stuck_out_tongue:

So it is decided - entities must be separate objects moving on a tilemap. They will use global position for movement, but they will also need to always translate their real position into tilemap position. This is best done by setget

var tilepos = Vector2(0,0) setget gettile

func process():
       self.tilepos = Tilemap.world_to_map(global_position)

gettile(value):
      if tilepos != value:
            tilepos = value
            #here goes all code to check surrounding tiles
            #and possibly a code to update entity position in dict

All is left is to design how are entities going to move alltogether. Will they appear simultanously and move in the same speed or rhytm ? What is going to change their movement ?

Inces | 2022-07-21 19:54

Each entity has its own movement speed, but it is possible for 2 instances of the same entity to have the same movement speed

zephy | 2022-07-22 05:01

If they may have different speeds and move non-simultanously, I would go for collision objects. Let them detect collisions with each other, by making them kinematic bodies or areas. They don’t need to collide with tilemap, but it also might be a good sollution.

Originally it looked as your project is some tilemap puzzle, but now it seems to be very classic. So Zombie objects collides with Bear object and their animation begin. No need to check surrounding tiles. Checking surrounding tiles may be useful if You want to know about collision ahead of time.

Inces | 2022-07-22 12:40

So there is no way to implement this without using a kinematic body or area2d? I already implemented a collision system for the entities but it was very buggy.

zephy | 2022-07-23 10:44

tilemap alone is only good for very stiff movement, like tic tac toe or classic ASCII roguelikes. If You want any visual aesthetics You shouldn’t go this way.
You need to combine tilemap functionality with dynamic collisions, and it will not be buggy. Imagine something like this :

On entitity colliding with another entity :
       if checksurroundingtiles() detects empty walkable tiles around :
               change movement direction towards one random detected walkable tile

This way You don’t have to rely on physics processing and buggy slide movement, but use singular properties of physics engine to enforce stable moevement rules

What movement behavior do You have in mind ?

Inces | 2022-07-23 12:32

Okay, then I will reimplement the collision handling with a kinematic body. The behavior is actually pretty simple if a bear collides with a zombie then start the attack attack animation and have them attack each other until one dies. I don’t even need to check for surrounding tiles.

zephy | 2022-07-23 12:42

But You may want them to approach each other in a way they stay in their tiles, not overlapping. So You will need check of surrounding tiles to detect future collision and make entity wait or go. You will also need tile detection to predict collision with unwalkable tile, so entity may take a turn before it collides and it won’t jitter into a wall

Inces | 2022-07-23 13:48

How would I be able to have them stay in their tiles and not overlap? Do you think some sort of line of sight algorithm would be good too?

zephy | 2022-07-23 14:35

Whenever your entity starts moving towards a direction, it can update its position in your stabledata for the future tile. So whenever another entity is going to approach a new tile, it will check stabledata if it can move to this position ( is there a wall, or is there another entity just walking into it ). If it detects another entity coming, it can wait in the center of its tile, until another entity reaches the center of its tile, and then both play the animation.

You might have seen something similar in old RTS like warcraft2. When 2 units collide in ugly angle, one of them makes a little stop, another one positions itself in fixed distance. This is also a system of a quare tilemap working with collision physics

Inces | 2022-07-23 19:53

:bust_in_silhouette: Reply From: Pomelo

Inces awnser is spot on, these are just some tips to go along.

First of all, if you need to hold more info per tile (for example, has zombie and is water) consider having other dictionaries (like a json) or arrays as values like so:

var grid : Dictionary = {
    Vector(0,0) : {
        object : zombie,
        terrain : forest,
        has_trap : true,
        walkable: false
        }
    Vector(1,0) : {
        object : wall,
        terrain : grass,
        has_trap : false,
        walkable : false
        }
    ...
}

One advtange of doing this, is that when you later start to code all the logic, for example, if you can walk in the tile or not, you dont have to check if it has a bear, a shark, a castle, etc… you just check for example, if grid[Vector(x,y)].walkable.

Aside from that, how to to do the checks, and how to change info is 100% dependant on your game, and up to you. For example if a “fight” has to happen always that 2 units end up one next to the other, I would do this:

first, I would put in the object key, an actuall class or node that represents the entity. The important thing is to be able to check if the object is of type say “unit”. You also could have a setter function in the grid var, in order to trigger checks when the grid is changed (you could also do the checks based on player input for example). Then i would do this:

func get_neighbors(tile_pos) -> Array:
    # this returns an array holding the 4 neighbors of the given tile

func has_unit(tile_pos) -> bool:
    # returns true if the object value is of type unit

func should_battle(tile_pos) -> bool:
    for tile in get_neighbors(tile_pos):
        if has_unit(tile):
            # trigger the fight 

In this example you would check for should_battle()after a unit moves, using of course the position the unit moved to.

Last advice, making this types of games involves a lot of logic, so they could be quite daunting. I have great love for them though, so if you too, power on!

    

Hey Pomelo! Thank you for sharing your knowledge! Just like with Inces, I forgot to mention that my entities are not bound by player movement instead they just move until they hit a wall and change direction based on it. So in the grid dict above, would I have to loop over all the positions and update their object key? Thank you for the extra push I will power on!

zephy | 2022-06-17 22:39

Hey, no prob! No you wouldnt need to loop over the dict to change its values. If you are not familiar with dicts, I would recomend reading the Documentation. In summary you just do grid[Vector2(x,y)].object = zombie if you want a zombie at x, y

Of course if the zombie was at, say i, j, before moving, you would also need to do grid[Vector2(i,j)].object = null. Here is when you need to start making lots of functions like for example func move_entity(start_pos, end_pos) that deals with everything that needs to be dealth with when moving a unit (like setting “walkable” to true or false). Then you also need to make other functions to make the “IA” decide in which direction to move, and until what point, etc…

As I said, its quite a bit haha

Pomelo | 2022-06-18 02:34

Ah I see, I will definitely read up the documentation this weekend. Thanks again!

zephy | 2022-06-18 02:44

Hey, reading your comment on inces awnser, I understand you have multiple units moving at the same “time”. If thats the case, I would suggest having a tick system (like a global counter that goes up), where esentially everything in the world knows when a tick has passed and then they do something. To avoid having a mess with multiple stuff moving around, I would have them act in “turns” (you would still see everything happening at the same time, but in the logic, two different units wont move and ocupy a same tile). In order to do this, when a tick advances, it triggers a, for example, do_turn() in every unit. you could have the units in an array and do:

func _on_tick_up():
    for unit in units:
        unit.do_turn()

I dont know the details of your game, but if you are dealing with grid movement and interactions, and in the mean time there is no player input, you wouldnt need to be checking stuff every frame, so no real need to use _procces()

Pomelo | 2022-06-18 13:51

Hey pomelo, do you think it would be a better idea to create separate scenes for tiles instead of using the tilemap?

zephy | 2022-07-12 03:03