Tomkun Where do you populate the invisibletiles array? At the start it needs to contain the coords of all cells in the map, because you start with all of them being invisible.

Umm you also don't need to do that check in _tile_data_runtime_update(). That's already handled in _use_tile_data_runtime_update(). Everything that passes that check needs its alpha set to 0.

    xyz
    Oh yeah, that's a leftover from when I didn't realise I didn't have to reset the modulation back to 1.
    I populate the array from the same script I use to generate the dungeon. It's very simple:

    func HideCells():	
    	for cell in hiddenfloorlayer.get_used_cells():
    		hiddenfloorlayer.invisibletiles.push_back(cell)
    • xyz replied to this.

      Tomkun Put some prints in the reveal_tile to see if anything actually gets erased. Always put some prints when the code is not behaving as expected.

      I'd suggest starting with a simpler version where only the tile the player is on gets revealed. That way you can be sure that tilemap update is actually working. Then proceed to implement a more complex algorithm for deciding which tiles to reveal.

        xyz
        I can get the tiles to reveal themselves reliably. That's not the issue any longer. What I want to improve is the algorithm I'm using to choose which tiles to reveal. At the moment it's revealing all tiles in a straight line, but I'd prefer a cone, or even a complete 360, but from what I can understand, raycasting wouldn't be an efficient way to do that.

        • xyz replied to this.

          Tomkun Well that's another problem altogether.
          Use a cone (or a circle) collider on the player to get all cells in range and then test each for visibility via a raycast. Worry about efficiency when you profile it. I doubt it will be an issue especially since you don't need to do it every frame. For cone you need to check only when player changed the movement direction or entered another tile. For a circle it's even less, you need to check only when player enters another tile.

          Is there a signal or something to use to make it fire when a new tile is entered, or does the _use_tile_data_runtime_update(coords) function take care of that?

          Oh, never mind, just realised I can do it with physics layers.

          • xyz replied to this.

            Tomkun You can use some collision but it's simpler to just check player's position. You always know at which cell the player is (via local_to_map()). Just maintain that value, get it every frame. If there's a difference compared to what is stored from the previous frame, the change happened.

              xyz
              Ah, okay that makes sense.
              I was thinking of using the hero's collision shape and a physics layer. Your idea sounds much more efficient. Is raycasting the best method for determining line-of-sight? I'm worried about it affecting performance.

              • xyz replied to this.

                Tomkun Raycasting is fast. It's faster than most other collision techniques. It's also pretty much the only sensible approach to doing this. As I said, lave the worries about it for after you've done some performance profiling.

                I'm having so much trouble with it though. Sometimes it doesn't detect walls, sometimes it does but local_to_map() converts to the wrong cell. It's being a royal pain to figure out.

                I think what is happening is the if the raycast collides with a tile to the left or top, using local_to_map() rounds up so it gets detected as either one cell too low or to the right.

                • xyz replied to this.

                  Tomkun Always cast from tile center to tile center. Add half a tile size to what you calculate using local_to_map(). This shouldn't be complicated.

                    xyz
                    I am already casting from center to center. That's the default setting of map_to_local().
                    I am getting collisions detected on tiles that aren't even between the player and the target tile. Unfortunately, I can't find any pattern to it yet, so I can't figure out why it's happening.

                    func reveal_tile(coords):
                    	#print (coords)
                    	var radius = 1
                    	var x_start = coords.x - radius
                    	var x_end = coords.x + radius + 1
                    	var y_start = coords.y - radius
                    	var y_end = coords.y + radius + 1
                    	print ("Hero Coords: ",coords)
                    	for x in range(x_start,x_end):
                    		for y in range(y_start,y_end):
                    			print ("Target Cell: ",Vector2i(x,y))
                    			if Vector2i(x,y) in invisibletiles:
                    				#tile to hero
                    				#raycast.global_position = self.map_to_local(Vector2i(x,y))
                    				#raycast.target_position = self.map_to_local(coords) - raycast.global_position
                    				#hero to tile
                    				raycast.global_position = self.map_to_local(coords)
                    				raycast.target_position = self.map_to_local(Vector2i(x,y)) - raycast.global_position
                    				#print (BetterTerrain.get_cell(self,raycast.get_collision_point()))
                    				#print (Vector2i(x,y))
                    				#print (self.local_to_map(raycast.get_collision_point()))
                    				#print (raycast.get_collision_normal())
                    				if !raycast.is_colliding() || Vector2i(x,y) == self.local_to_map(raycast.get_collision_point()):
                    					print("Clear")
                    					invisibletiles.erase(Vector2i(x,y))
                    					notify_runtime_tile_data_update()
                    				else:
                    					#print ("Hero Coords: ",coords)
                    					#print ("Target Cell: ",Vector2i(x,y))
                    					print ("Collided   : ",self.local_to_map(raycast.get_collision_point()))
                    			else:
                    				print ("Already Revealed")
                    • xyz replied to this.

                      Tomkun Raycasting works in global space. map_to_local() returns positions in tilemap's local space. If tilemap has any transforms on itself, there will be mismatch.

                        xyz
                        Oh now that's interesting.
                        I am not doing any transforms, but when I set up the navigation I was getting strange results because of BetterTerrains.
                        I will try without that.

                        • xyz replied to this.

                          Tomkun You just need to make sure that any positions you deal with are properly transformed from local to global space and vice versa.

                          Right, I have all the raycasting worked out and removing the correct tiles from the list, but still occasionally some tiles won't update. Seems to happen most when you enter a relatively large room (but not always). Once this has happened, they will never update again until the map is regenerated.
                          Is it possible for _tile_data_runtime_update() to get choked up and not update the tiles?

                          Okay, I really need some more help.
                          As far as I can tell, my code is working just fine.
                          The raycasts are returning the cells that are in line-of-sight of the hero and removing them from the invisibletiles array.
                          I then call notify_runtime_tile_data_update() which triggers the update and the tile appears.
                          So far, so good.

                          However, I have noticed that occasionally a tile will not appear, even though it has been removed from the array.
                          I have created a helper function to troubleshoot:

                          func double_check(target : Vector2) -> void:
                          	var cell = self.local_to_map(target)
                          	print(cell)
                          	if cell in invisibletiles:
                          		print ("invisible -> visible")
                          		invisibletiles.erase(cell)
                          		notify_runtime_tile_data_update()
                          	else: 
                          		print("visible -> invisible")
                          		invisibletiles.append(cell)
                          		notify_runtime_tile_data_update()

                          This allows me to trigger the tiles visibility at will, however those tiles will still not become visible.
                          Unless... I hide another tile somewhere else and then suddenly it will start to work! Although now the tile that I 'turned off' will no longer turn back on...

                          Now, I don't understand this behaviour at all. If I understand correctly, the function:

                          func _tile_data_runtime_update(_coords, tile_data):
                          	tile_data.modulate.a = 0

                          Is making the tiles invisible, so if this function does not run on the tile, it should be visible by default, but it isn't. The tile has been removed from the array and therefore this function is not being called on it.

                          • xyz replied to this.

                            Tomkun How does it work if you return true for all populated cells and then in update function set alpha according to visibles (or invisibles array)?

                              xyz

                              At first glance, that seems to have fixed it! Any idea why it wasn't reliable before?

                              • xyz replied to this.

                                Tomkun If true is not returned for any tile (which is the case when you remove the last tile from invisible list) the engine will apparently not bother to do any updates at all. Needs some more testing, though. You can sorta get around it by always keeping at least one dummy cell in the list of invisible cells. Or update everything. Just don't do it every frame.