How do i deal with camera leaving camera limits while dragging

Godot Version

v4.5.1.stable.official [f62fdbde1]

Question

Hey, how to do if camera position leaves it`s limits while dragging

Camera code:
extends Node2D
class_name BoardMovement

@export var zoom_min: float = 0.5
@export var zoom_max: float = 2.0
@export var zoom_increment: float = 0.1

@onready var _camera: Camera2D = $Camera2D

@onready var _viewport_size: Vector2 = get_viewport().size
var _target_zoom: float = 1.0
var _target_global_position: Vector2

func _input(event: InputEvent) → void:
if event is InputEventMouseMotion:
if event.button_mask == MOUSE_BUTTON_MASK_MIDDLE:
_camera_drag(event)
if event is InputEventMouseButton:
if event.is_pressed():
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
_zoom_out()
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
_zoom_in()
##this one is for dragging
func _camera_drag(event: InputEventMouseMotion) → void:
_target_global_position -= event.relative * (Vector2.ONE / _camera.zoom)
_target_global_position.x = clamp(
_target_global_position.x,
_camera.limit_left+_viewport_size.x/2*(1/_camera.zoom.x),
_camera.limit_right-_viewport_size.x/2*(1/_camera.zoom.x))
_target_global_position.y = clamp(
_target_global_position.y,
_camera.limit_top+_viewport_size.y/2*(1/_camera.zoom.x),
_camera.limit_bottom-_viewport_size.y/2*(1/_camera.zoom.x))
global_position = _target_global_position

func _zoom_in() → void:
_target_zoom = _camera.zoom.x + zoom_increment
_target_zoom = clamp(_target_zoom, zoom_min, zoom_max)
if _target_zoom != _camera.zoom.x:
var tween = create_tween()
tween.tween_property(_camera, “zoom”, _target_zoom * Vector2.ONE, 0.25)

func _zoom_out() → void:
_target_zoom = _camera.zoom.x - zoom_increment
_target_zoom = clamp(_target_zoom, zoom_min, zoom_max)
if _target_zoom != _camera.zoom.x:
var tween = create_tween()
tween.tween_property(_camera, “zoom”, _target_zoom * Vector2.ONE, 0.25)
##this one doesnt matter much
func position_in_the_middle(node_size: Vector2):
var parent_global = get_parent().global_position if get_parent() else Vector2.ZERO
global_position = parent_global + node_size/2
_target_global_position = global_position
##this one doesnt matter much neither
func set_camera_limits(start_point: Vector2, end_point: Vector2):
_camera.limit_left = int(start_point.x)
_camera.limit_top = int(start_point.y)
_camera.limit_right = int(end_point.x)
_camera.limit_bottom = int(end_point.y)
print(start_point)
print(end_point)
global_position.x = clamp(global_position.x, start_point.x, end_point.x)
global_position.y = clamp(global_position.y, start_point.y, end_point.y)
_target_global_position = global_position

#func _physics_process(_delta: float) → void:
#print(_camera.limit_top)
#print(_camera.limit_left)
#print(_camera.limit_bottom)
#print(_camera.limit_right)
#print(global_position)
#print(position)

Basically i tried limitting it`s position considering viewport size, zoom, but is there any other optimal way to do that? There is no much information about it on the web.

What happened to all your code indentation? That’s hard to read. Also, I think you may be using camera limits wrong in an unintended way? You set an area, and the camera cannot leave that area. So you have a camera that shows the little rectangle inside the larger visible area.

____________
|    __    |
|   |__|   |
|__________|

Your limits should just be set to the outside rectangle. They don’t need to be changed based on the amount of camera zoom. If you zoom in, the inner rectangle gets bigger. If you zoom out, it gets smaller. If the inner rectangle can become bigger than the outer rectangle, you cannot stop things from being shown.

This is what my camera code typically looks like (without zooming). (Game autoload separate script.) You just pass the tile_map_layer you want to be the boundaries. Alternately, you could pass a Rect2i.

class_name BoundCamera2D extends Camera2D


func _ready() -> void:
	Game.update_camera_boundaries.connect(_update_camera_boundaries)


## Sets the camera's boundaries to be that of the used tiles on the passed
## TileMapLayer.
func _update_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
1 Like

Thank you, sorry for previous code, here is code with identation:

extends Node2D
class_name BoardMovement

@export var zoom_min: float = 0.5
@export var zoom_max: float = 2.0
@export var zoom_increment: float = 0.1

@onready var _camera: Camera2D = $Camera2D

#@onready var _viewport_size: Vector2 = get_viewport().size
var _target_zoom: float = 1.0
var _target_global_position: Vector2



func _input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		if event.button_mask == MOUSE_BUTTON_MASK_MIDDLE:
			_camera_drag(event)
	if event is InputEventMouseButton:
		if event.is_pressed():
			if event.button_index == MOUSE_BUTTON_WHEEL_UP:
				_zoom_out()
			if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
				_zoom_in()

## here
func _camera_drag(event: InputEventMouseMotion) -> void:
	_target_global_position -= event.relative * (Vector2.ONE / _camera.zoom)
	##
	## without this part, camera position leaves limites
    ##but keep showing the picture inside. When camera leaves limites its
	## hard to drag it back.
	##
	#_target_global_position.x = clamp(
		#_target_global_position.x, 
		#_camera.limit_left+_viewport_size.x/2*(1/_camera.zoom.x),
		#_camera.limit_right-_viewport_size.x/2*(1/_camera.zoom.x)) 
	#_target_global_position.y = clamp(
		#_target_global_position.y, 
		#_camera.limit_top+_viewport_size.y/2*(1/_camera.zoom.x), 
		#_camera.limit_bottom-_viewport_size.y/2*(1/_camera.zoom.x))
	##
	global_position = _target_global_position


func _zoom_in() -> void:
	_target_zoom = _camera.zoom.x + zoom_increment
	_target_zoom = clamp(_target_zoom, zoom_min, zoom_max)
	if _target_zoom != _camera.zoom.x:
		var tween = create_tween()
		tween.tween_property(_camera, "zoom", _target_zoom * Vector2.ONE, 0.25)


func _zoom_out() -> void:
	_target_zoom = _camera.zoom.x - zoom_increment
	_target_zoom = clamp(_target_zoom, zoom_min, zoom_max)
	if _target_zoom != _camera.zoom.x:
		var tween = create_tween()
		tween.tween_property(_camera, "zoom", _target_zoom * Vector2.ONE, 0.25)


func position_in_the_middle(node_size: Vector2):
	var parent_global = get_parent().global_position if get_parent() else Vector2.ZERO
	global_position = parent_global + node_size/2
	_target_global_position = global_position


func set_camera_limits(start_point: Vector2, end_point: Vector2):
	_camera.limit_left = int(start_point.x)
	_camera.limit_top = int(start_point.y)
	_camera.limit_right = int(end_point.x)
	_camera.limit_bottom = int(end_point.y)
	global_position.x = clamp(global_position.x, start_point.x, end_point.x)
	global_position.y = clamp(global_position.y, start_point.y, end_point.y)
	_target_global_position = global_position
	

I also attached video to demonstrate this:

(the limits are set on the board boundaries)

The camera position leaves it`s limits but keep showing the picture inside of the limites. Its hard to drag it back.
I guess that might be because of the dragging function (_camera_drag), maybe there is another way to implement it? Is there any built-in way to implement dragging so camera position doesnt leave limits too? I used some tutorials but they were for Godot 3 or Godot 4.1
Thank you again

So I looked at old code, and this is what I did. Keep in mind I’m handling touch screen dragging, panning with the right stick on a controller, and edge scrolling (moving the camera when the mouse gets close to the edge of the screen).

Basically, you just want to clamp the values whenever you change them so that they never go outside your boundaries.

const PAN_SPEED = 40.0
const TOUCH_PAN_SPEED = 1.0
const EDGE_SCROLL_MARGIN = 50.0
const EDGE_SCROLL_SPEED = 20.0

var _camera_boundaries: Rect2 #Use the Rect2 function from above and return a value to get this. I was passing it through an autoload function.
var touch_points: Dictionary = {}


func _physics_process(_delta: float) -> void:
	var new_position = _handle_panning()
	if new_position == Vector2.ZERO:
		new_position = _handle_edge_movement()
	position = (position + new_position).clamp(_camera_boundaries.position, _camera_boundaries.end)


func _input(event: InputEvent) -> void:
	if event is InputEventScreenDrag:
		touch_points[event.index] = event.position
		if touch_points.size() == 1:
			var new_position = -event.relative * TOUCH_PAN_SPEED
			position = (position + new_position).clamp(_camera_boundaries.position, _camera_boundaries.end)


func _handle_panning() -> Vector2:
	var pan_direction = Input.get_vector("pan_camera_left", "pan_camera_right", "pan_camera_up", "pan_camera_down")
	return pan_direction * PAN_SPEED


func _handle_edge_movement() -> Vector2:
	var mouse_pos = get_viewport().get_mouse_position()
	var visible_screen_size = get_viewport().get_visible_rect().size
	var pan_direction := Vector2.ZERO
	
	if mouse_pos.x < EDGE_SCROLL_MARGIN:
		pan_direction.x -= 1
	elif mouse_pos.x > visible_screen_size.x - EDGE_SCROLL_MARGIN:
		pan_direction.x += 1
	
	if mouse_pos.y < EDGE_SCROLL_MARGIN:
		pan_direction.y -= 1
	elif mouse_pos.y > visible_screen_size.y - EDGE_SCROLL_MARGIN:
		pan_direction.y += 1
	
	return pan_direction * EDGE_SCROLL_SPEED
1 Like

Thank you! I guess i can use setter for target_position to clamp

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.