Camera2d limits not limiting zoom on all axes

Godot Version

4.2.1 stable

Question

Hi! First time using this forum so excuse any misstakes I make in writing/categorising.

Like the title says, my Camera2D node has limits set up using the limit section in the inspector. The limits I’ve set are Right (640 + 250 = 890) and Left (-250) which should make panning possible 250 px to the right and left respectively(?). The project is running in 640x360.

The issue I’ve got is when I zoom out the camera. Now if both of the limits are set like above, then the camera limits properly to the right (the camera physically cant see more than 250 to the right) but can sadly keep zooming out infinitely in the left direction and ignores the left limit I’ve set completely. If I instead remove the right limit and set it to a large number, then the left limit works.

My issue is that I’d like it to stop being possible to zoom out if both limits are reached, right now only one limit works at a given time, with the right taking precedence for some reason over the left.

I code in GDScript. Here’s the simple code used for zooming the cam in and out:

			MOUSE_BUTTON_WHEEL_UP:
				zoom_level = clamp(zoom_level + zoomIncrement, zoomMin, zoomMax)
				zoom = zoom_level * Vector2.ONE
				get_tree().get_root().set_input_as_handled()
			MOUSE_BUTTON_WHEEL_DOWN:
				zoom_level = clamp(zoom_level -	zoomIncrement, zoomMin, zoomMax)
				zoom = zoom_level * Vector2.ONE
				get_tree().get_root().set_input_as_handled()

Any help appreciated! Thanks in advance!

The camera limits are a very basic implementation of the idea of not letting the camera move too far. This doesn’t really work when you have the ability to zoom. If you just clamp the camera position to be within the limits after you zoom, in your own code, it will work as you expect it. Just remember to take half the viewport size into account in your calculation.

Yes, that’s what I figured too and I tried to implement something like what you said but couldn’t get it to work at all sadly. I’d really appreciate code examples, quite new to Godot as a whole.

Post your entire script, then. It will be easier to see what you are already doing.

I never got very far, I wrote some simple logic but failed to convert it to code:

panMaxRight is 640 + 250 (890), panMaxLeft is -250

640*(1+1-x) = 890 x = wantedMinZoom, x is between 0-1 (zoom scale)

Find x, find what the minimumZoom should be set to before showing more than the right and left pan maxes. Thank you!

When I said post your script I meant post the whole file, from extends Camera2D to the end, because that makes it easier to give you a better answer, please.

Alright, but most of it is unrelated:

extends Camera2D

@export var zoomIncrement = 0.05
@export var zoomMin = 0.5
@export var zoomMax = 2.0	
@export var panMaxRight = 10
@export var panMaxLeft = -10
@export var panMaxUp = -50
@export var panMaxDown = 50

var panning:= false
var zoom_level:= 1.0

var startPos

func _ready():
	startPos = global_position.x
	panMaxRight += startPos
	panMaxLeft += startPos

func _process(delta):
	#zoomMin = #value betwen 0 - 1, res is 640, if zoom level 0.5 then "res" is 1280
	#panMaxRight is 640 + 250 (890), ##panMaxLeft is -250
	#640*(1+1-x) = 890   x = wantedMinZoom, x is btwen 0-1

	#print((1 + 1-zoom.x) * 640)
	pass
	#if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):# and Input.is_key_pressed(KEY_CTRL):
	#	panning = true
#	else:
	#	panning = false
	#print(panning)
#func _input(event: InputEvent) -> void:
	#if event is InputEventMouseButton:					# Read Mouse Buttons
		#print ("Mouse Button Event = ", event.as_text())

func _input(_event: InputEvent) -> void:
	if _event is InputEventMouseButton:
		match _event.button_index:
			MOUSE_BUTTON_MIDDLE:
				if _event.pressed:
					panning = true
				else:
					panning = false
				get_tree().get_root().set_input_as_handled()
			MOUSE_BUTTON_WHEEL_UP:
				zoom_level = clamp(zoom_level + zoomIncrement, zoomMin, zoomMax)
				zoom = zoom_level * Vector2.ONE
				#print((1 + 1-zoom.x) * 640)
				get_tree().get_root().set_input_as_handled()
			MOUSE_BUTTON_WHEEL_DOWN:
				zoom_level = clamp(zoom_level -	zoomIncrement, zoomMin, zoomMax)
				zoom = zoom_level * Vector2.ONE
				#Zooming out, if has zoomed out enough then cam limits will be hit!
				#print((1 + 1-zoom.x) * 640)
				get_tree().get_root().set_input_as_handled()
	elif _event is InputEventMouseMotion and panning:
		get_tree().get_root().set_input_as_handled()
		if Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE):# and Input.is_key_pressed(KEY_CTRL):
			# Panning rn
			var posMinusY: Vector2 = _event.relative
			if(offset.y > panMaxUp && posMinusY.y > 0) || (offset.y < panMaxDown && posMinusY.y < 0):
				offset.y -= posMinusY.y
			posMinusY.y = 0
			# Fix så inte kan dra snabbt för att komma längre v/r, (se om nya gp skulle gå förbi panMaxR)
			if (global_position.x < panMaxRight && posMinusY.x < 0) || (global_position.x > panMaxLeft && posMinusY.x > 0):
				global_position -= posMinusY / zoom_level
		else:
			panning = false

I am not super confident this is what you need, but try it. =3

extends Camera2D

@export var zoomIncrement = 0.05
@export var zoomMin = 0.5
@export var zoomMax = 2.0	
@export var panMaxRight = 10
@export var panMaxLeft = -10
@export var panMaxUp = -50
@export var panMaxDown = 50
# private vars for the corners of the limit area we set with the exported vars
var _panNW: Vector2
var _panSE: Vector2


var panning:= false
var zoom_level:= 1.0

var startPos

func _ready():
	startPos = global_position.x
	panMaxRight += startPos
	panMaxLeft += startPos
	_panNW = Vector2(panMaxLeft, panMaxUp)
	_panSE = Vector2(panMaxRight, panMaxDown)

func _process(delta):
	#zoomMin = #value betwen 0 - 1, res is 640, if zoom level 0.5 then "res" is 1280
	#panMaxRight is 640 + 250 (890), ##panMaxLeft is -250
	#640*(1+1-x) = 890   x = wantedMinZoom, x is btwen 0-1

	#print((1 + 1-zoom.x) * 640)
	pass
	#if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):# and Input.is_key_pressed(KEY_CTRL):
	#	panning = true
#	else:
	#	panning = false
	#print(panning)
#func _input(event: InputEvent) -> void:
	#if event is InputEventMouseButton:					# Read Mouse Buttons
		#print ("Mouse Button Event = ", event.as_text())

func _input(_event: InputEvent) -> void:
	# get_tree().get_root() returns the same object as get_viewport() by default,
	# but this is more correct if we ever use sub-viewports.
	var viewport = get_viewport()
	if _event is InputEventMouseButton:
		match _event.button_index:
			MOUSE_BUTTON_MIDDLE:
				if _event.pressed:
					panning = true
				else:
					panning = false
				viewport.set_input_as_handled()
			MOUSE_BUTTON_WHEEL_UP:
				zoom_level = clamp(zoom_level + zoomIncrement, zoomMin, zoomMax)
				zoom = zoom_level * Vector2.ONE
				#print((1 + 1-zoom.x) * 640)
				viewport.set_input_as_handled()
			MOUSE_BUTTON_WHEEL_DOWN:
				zoom_level = clamp(zoom_level -	zoomIncrement, zoomMin, zoomMax)
				zoom = zoom_level * Vector2.ONE
				#Zooming out, if has zoomed out enough then cam limits will be hit!
				#print((1 + 1-zoom.x) * 640)
				limit_panning()
				viewport.set_input_as_handled()
	elif _event is InputEventMouseMotion and panning:
		viewport.set_input_as_handled()
		if panning:# and Input.is_key_pressed(KEY_CTRL):
			# Panning rn
			limit_panning()
		else:
			panning = false

func limit_panning():
	# Just keep the camera position within the limits. clamp() on Vector2 worck component-wise.
	position = clamp(position, _panNW, _panSE)

Wow, thank you so much! I’ll try it soon and let you know.

Hmm, that didn’t work, when panning to the max dist either direction the camera either snaps up or down from that far left/right position, and zoom is sadly like before where the limits are neglected.

Post your project, I guess your setup is different from what I assumed.

I’m not really comfortable with that, I’ve got art that I wouldn’t want shared. If that means you can’t help further then I understand that. Is there anything else I can give to help? Camera2D is in position x 320 y 180, and has the posted script attached to it.