Help With Screen Clamping

Godot Version

4.2.1

Question

Hey everyone,

I’m trying to figure out a way of preventing my character from flying off the screen. I have managed to sort of setup some bounds but they’re not in line with the viewport. The character hits the boundary at the left and bottom of the screen but there is none for the top and right side.

I’m very new to GD Script (and programming in general) so some help with this would be greatly apprciated! I’ve attached a snippet of my code.

Thanks!

Please paste your code here instead of using a screenshot. You can use the </> button here to wrap the code in triple backticks (```) to format it properly.

Firstly, I feel like you might prefer to set up some physical walls with collision shapes, instead of relying on clamp(). You can use StaticBody2D nodes (with a WorldBoundaryShape2D) for this. Refer to the documentation here: Physics introduction — Godot Engine (stable) documentation in English

But if you want to use clamp, I suspect your problem is related to how get_viewport().size works. The viewport size is the size of physical pixels on the screen that the actual window takes up. However, this might not match your game’s actual units, for example if you apply any zoom or stretch/maximize the window.

So, to get the actual size of the visible area, you should use get_viewport_rect().size instead.

Additionally, you should perform the clamping after calling move_and_slide(), since move_and_slide() will change the position. Otherwise your character will appear to sink into the edges a bit if the player holds a direction.

Thanks for response!

I have tried using get_viewport_rect().size but I keep getting an error saying "Function “get_viewport_rect()” not found in base self.

1 Like

Sorry, I hadn’t noticed you were using CharacterBody3D instead of CharacterBody2D.

Try using get_viewport().get_visible_rect().size instead.

Sadly, that get’s me the same result…

Also, here is my code correctly formatted (hopefully!)

extends CharacterBody3D

const SPEED = 5.0
const ACCELERATIION = 10.0
const ROTATION_SPEED = 5.0


func _physics_process(delta):
	
	# Movement controls
	var inputMovementVector = Vector3.ZERO
	var screenSize =  get_viewport().get_visible_rect().size
	
	inputMovementVector.y = Input.get_action_strength("Up") - Input.get_action_strength("Down")
	inputMovementVector.x = Input.get_action_strength("Right") - Input.get_action_strength("Left")
	
	inputMovementVector = inputMovementVector.normalized()
	
	velocity.y = inputMovementVector.y * SPEED
	velocity.x = inputMovementVector.x * SPEED
	
	# Rotation controls
	rotation_degrees.z = velocity.x * -2
	rotation_degrees.x = velocity.y * -2
	rotation_degrees.y = velocity.x * -2
	
	move_and_slide()
	
	# Screen clamping	
	position.x = clamp(position.x, 0 , screenSize.x)
	position.y = clamp(position.y, 0 , screenSize.y)

How is your Camera3D set up?


Like this

1 Like

Okay, I think I’ve got a better idea of what you’re trying to do here.

Just to clarify my understanding: You’ve got a 3D character, which you’re moving around on the X and Y axis. You’ve got a 3D camera pointed towards the character. You want to limit the character’s movement to stay within the camera’s visible area.

Is that correct?

If so, because your camera is using a perspective projection, you’ll need to do some basic matrix math to make this happen. This is because the screen pixels aren’t relevant to the 3D world space.

Fortunately, Camera3D provides some handy methods to do all the work for that.

Try this clamping code:

	# Screen clamping
	
	# Get the current 3D camera.
	var camera := get_viewport().get_camera_3d()
	
	# Figure out how far the player currently is from the camera (along the camera's forward axis).
	var player_pos_relative_to_camera := camera.global_transform.inverse() * global_position
	var z_depth := -player_pos_relative_to_camera.z
	
	# Get the world-coordinate corners of the visible area aligned with the player character.
	var upper_left := camera.project_position(Vector2(0, 0), z_depth)
	var bottom_right := camera.project_position(get_viewport().get_visible_rect().size, z_depth)
	
	position.x = clamp(position.x, upper_left.x, bottom_right.x)
	position.y = clamp(position.y, bottom_right.y, upper_left.y)

I tried my best to recreate your scene, and the above code looks like this:

I hope I’m getting close!

Hey! Sorry for the late response!

That seems to have worked! Thank you :grin:
I do have some questions, since I’m very new to GD Script/programming in general I would just like to try and understand a few things if that’s okay?

  • Camera’s forward axis. Is the the Z axis? as that is the axis that points forward from the camera?
  • I’m not sure HOW the script is determining the upper left and bottom right.

Thank you so much!