It looks like your issue is potentially in code you didn’t show, which is whatever is generating the Array data for the TileMapBoundsChanged
signal.
This is the code I use to do the same thing:
class_name CharacterBoundCamera2D extends Camera2D
## Sets the camera's boundaries to be that of the used tiles on the passed
## TileMapLayer.
func change_camera_boundaries(tile_map_layer: TileMapLayer) -> void:
var map_boundaries: Rect2i = tile_map_layer.get_used_rect()
var tile_size: Vector2i
tile_size.x = tile_map_layer.tile_set.tile_size.x
tile_size.y = tile_map_layer.tile_set.tile_size.y
map_boundaries.position *= tile_size
map_boundaries.size *= tile_size
limit_top = map_boundaries.position.y
limit_left = map_boundaries.position.x
limit_bottom = map_boundaries.end.y
limit_right = map_boundaries.end.x
This is what I do in my level code:
class_name MapLevel extends MapLevel2D
@onready var game_camera_2d: CharacterBoundCamera2D = %GameCamera2D
@onready var platforms: TileMapLayer = %Platforms
func _ready() -> void:
super()
game_camera_2d.change_camera_boundaries(platforms)
I have a few constructive comments on your code, take them or leave them. Some are based on the Godot Style Guide. these guidelines make your code easier to read if anyone else is reading your code, like say you’re asking for help on a forum. Others are just recommended programming practices.
- Signal names should be
snake_case
, not PascalCase
.
- Function names should be
snake_case
, not PascalCase
.
pass
should never be in production code. It is only used to stub out a function that has no code so that you do not get compiler warnings.
- Private functions (ones not used outside your current class) should always start with an underscore (
_
), like _update_limits()
- In a function, avoid unnecessary casting as it is time-consuming. (More on this below.)
- When possible, use a clear data type when passing an argument. (See below.)
- Try to keep your functions atomic. (See below.)
So let’s talk more about 4, 5 and 6. Your function UpdateLimits()
takes an Array of Vector2s. It’s pretty clear from your code that you expect that Array to contain exactly two Vector2s. However, you only check to make sure the Array is not empty. You do not check to see if it only contains one Vector, or more than two. So you could get bad data. (Admittedly unlikely.) Regardless, a future user (which is 95% of the time you) of your function is going to have to go read your definition of your UpdateLimits()
function to know how to use it.
The second issue is that you are passing arrays of Vector2 instead of Vector2i. So now, you have to convert those values in your function instead of just using them. Not the end of the world, BUT your code would be cleaner if your function assumed that a function calling it would clean up their data rather than you cleaning it up for them. Furthermore, I happen to know that TileMapLayers store the information you want as integers. So even if you don’t realize you are doing it, you are wasting cycles converting all that data to floats, passing it, then converting it back to ints.
There are a few ways you can handle this. One is to create your own class called something like MapBoundary that contains this data and pass that.
class_name MapBoundary extends Resource
var top_left_bounds: Vector2i
var bottom_right_bounds: Vector2i
Then your function signature would look something like:
func _update_limits(bounds : MapBoundary):
Now as long as they pass you a MapBoundary, they have to know what’s in it, and you don’t have to check it’s valid (if your Resource checks for you). It means instead of reading your function code, they just have to create that object.
Second option is to look to see if the language you’re using has a primitive or object you can use that would server your purpose. Well, it turns out that Godot has a definition of a rectangle, and specifically one for integers called a Rect2i. So now we can write our signature as:
func _update_limits(bounds : Rect2i):
It’s pretty easy to parse and create these. In fact, it’s really easy with a TileMapLayer because there’s a built-in function called get_used_rect()
. So your _ready()
function can change:
func _ready() -> void:
LevelManager.tile_map_bounds_changed.connect(_update_limits)
_update_limits(LevelManager.current_tilemap.get_used_rect())
You’ll also have to change LevelManager which now no longer needs a current_tilemap_bounds
variable or the code to create it, and needs a change to the tile_map_bounds_changed
signal to pass in the Rect2i by calling TileMapLayer.get_used_rect()
itself.
This is where we get to talking about making your code atomic. Currently, your LevelManager code needs to understand how your PlayerCamera code works to pass it the right value. Even though we are no just passing a Rect2i, your LevelManager needs to know where that Rect2i comes from and how it’s constructed. Sure, in this case it’s not that complicated, but why does LevelManager need to know at all? It doesn’t.
We can simplify this even further by just passing the TileMapLayer itself. The obvious objection to that is that PlayerCamera doesn’t need all that information. It’s a waste of processing time and memory copying that whole thing over. Except Godot doesn’t do that. Godot passes variable by Reference what this means is that it passes the address in memory where your TileMapLayer is stored, which is a very small memory address. So the fact that you are only calling get_used_rect()
on it doesn’t matter. It’s no more expensive than your LevelManager doing it.
More importantly you can call LevelManager doesn’t need to know how PlayerCamera works. It just needs to know to pass the TileMapLayer. Putting all that together, we get this:
class_name PlayerCamera extends Camera2D
func _ready() -> void:
LevelManager.tile_map_bounds_changed.connect(_update_limits)
_update_limits(LevelManager.current_tilemap)
func _update_limits(tile_map_layer: TileMapLayer):
var map_boundaries: Rect2i = tile_map_layer.get_used_rect()
var tile_size: Vector2i = tile_map_layer.tile_set.tile_size
map_boundaries.position *= tile_size
map_boundaries.size *= tile_size
limit_top = map_boundaries.position.y
limit_left = map_boundaries.position.x
limit_bottom = map_boundaries.end.y
limit_right = map_boundaries.end.x
print(map_boundaries)