How to prevent the camera from moving off limits?

Godot Version

4.3.dev5

Question

I’m working on a top down 2d game where the player can move the camera using the WASD keys. I want to limit this camera movement to the limits of the camera.

As you can hopefully see in this GIF, the camera doesn’t show areas of the map that the tilemap does not cover. So this works just fine. The problem is that the position of the camera can exceed these limits. This means when you keep moving the camera out of limits it can take much longer to move the camera in the opposite direction. In the GIF, keep pressing S to move the camera down but for a while nothing happens.
How can I fix this behaviour? Is there some way to access the camera rect in world coordinates?

CleanShot 2024-04-22 at 12.52.03

class_name GameCamera
extends Camera2D

@export var lerp_smoothing := 10.0
@export var camera_speed := 25

@export var world_tile_map: TileMap:
	get():
		if not world_tile_map.is_node_ready():
			await world_tile_map.ready
		return world_tile_map


func _ready() -> void:
	var world_rect := world_tile_map.get_used_rect()
	limit_left =  world_rect.position.x * world_tile_map.tile_set.tile_size.x
	limit_top = world_rect.position.y * world_tile_map.tile_set.tile_size.y
	limit_right = world_rect.end.x * world_tile_map.tile_set.tile_size.x
	limit_bottom = world_rect.end.y * world_tile_map.tile_set.tile_size.y


func _process(delta: float) -> void:
	var movement_vector := Input.get_vector("move_left", "move_right", "move_up", "move_down")
	
	if not movement_vector == Vector2.ZERO:
		var target_position := global_position + movement_vector * camera_speed
		global_position = global_position.lerp(target_position, 1.0 - exp(-delta * lerp_smoothing))
1 Like

The problem is you’re moving the global position away from your limit position (remember, the camera global position is different from the target positon, what control what you actually see on this camera), so to solve that your need to check if the next position still inside the tilemap rect

extends Camera2D

@export var lerp_smoothing := 10.0
@export var camera_speed := 25
var world_rect: Rect2i

@export var world_tile_map: TileMap:
	get():
		if not world_tile_map.is_node_ready():
			await world_tile_map.ready
		return world_tile_map


func _ready() -> void:
	world_rect = world_tile_map.get_used_rect()
	var x_axis_size = ProjectSettings.get_setting("display/window/size/viewport_width")
	var y_axis_size = ProjectSettings.get_setting("display/window/size/viewport_height")
    world_rect = world_rect.grow_individual(-x_axis_size/2, -y_axis_size/2, -x_axis_size/2, -y_axis_size/2)


func _process(delta: float) -> void:
	var movement_vector := Input.get_vector("move_left", "move_right", "move_up", "move_down")
	
	if not movement_vector == Vector2.ZERO:
		var target_position := global_position + movement_vector * camera_speed
 		
        if world_rect.has_point(target_position):
            global_position = global_position.lerp(target_position, 1.0 - exp(-delta * lerp_smoothing))
1 Like

This code leads to a rect with a negative width and height for me. Did you encounter this problem too?

E 0:00:01:0252 game_camera.gd:30 @ _process(): Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.
<C++ Source> ./core/math/rect2i.h:131 @ has_point()
game_camera.gd:30 @ _process()

There works just fine (in my case i’m creating a Rect2i variable at postion 0, 0 with size of 5000x5000 to test). Your camera has a custom zoom value? That code expects the camera zoom as Vector2(1, 1).

1 Like

Maybe it’s because my TileMap rect is much smaller? Anyways, adding this line makes it work

world_rect = world_rect.abs()

Thanks a lot!