Tomkun And tile_data.modulate.a = .5 will make it half-transparent 😉
But you don't need to ever set it to 1. You just need to keep the list of invisible cells up to date, and call notify_runtime_tile_data_update() whenever this list is changed.

    xyz
    Oh, I see, so if the tile is removed from the list, then it will become visible automatically?
    No, that's not working... The tiles are being removed from the list but aren't becoming visible again.
    Forget it, I was being dumb again.

    Great news!
    Thanks to you, it's working (kind of)! I still need to iron out some kinks in my raycasting I think, but tiles are appearing now!

    • xyz replied to this.

      xyz
      Which part?
      I got the raycasting working, but I'm still not happy with the algorithm I'm using to decide which tiles to reveal.
      The code that reveals the tiles is basically exactly what you wrote:

      extends TileMapLayer
      
      var invisibletiles: Array[Vector2i]
      @onready var hero = $"../../Hero"
      
      func reveal_tile(collisionpoint: Vector2):
      	var coords = self.local_to_map(collisionpoint)
      	var herocoords = self.local_to_map(hero.position)
      	
      	var x_start = min(herocoords.x, coords.x)
      	var x_end = max(herocoords.x, coords.x)
      	var y_start = min(herocoords.y, coords.y)
      	var y_end = max(herocoords.y, coords.y)
      	for x in range(x_start,x_end + 1):
      		for y in range(y_start,y_end + 1):
      			if Vector2i(x,y) in invisibletiles:
      				invisibletiles.erase(Vector2i(x,y))
      				notify_runtime_tile_data_update()
      	
      func _ready():
      	pass
      
      func _use_tile_data_runtime_update(coords):
      	if coords in invisibletiles: return true
      	else: return false
      	
      func _tile_data_runtime_update(coords, tile_data):
      	if coords in invisibletiles:
      		tile_data.modulate.a = 0
      • xyz replied to this.

        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?