(Tutorial question) Why is the character half out of the screen with clamp?

Godot Version

4.2.1

Question

Hello all! I’m following Coding the player — Godot Engine (stable) documentation in English and there are a few things I don’t quite understand about how the character dimentions and clamp work together to keep the char in the boundaries of the screen. The thing that is confusing me the most is that my char ends up half out of the screen regardless of what I do.

Here is what I mean by “half out of the screen”:
image

It successfully stays within the boundaries but just half of it.

Here are my questions:

  1. Are the Player (Area2D) and the AnimatedSprite2D the same size?
  2. Is clamp meant to be applied in relation to the entire game viewport?
  3. Is clamp applied directly on restricting the Player (Area2D) ?
  4. If yes, is there some way I can make it take into the account the sprite dimensions?

I tried modifiying the line we get the screen_size to deduct a plain vector like:

screen_size = get_viewport_rect().size - Vector2(100,100)

I thought that simply reducing the “screen_size” dimentions by the character dimension should do the trick but subtracting a regular vector isn’t affecting this in any way (I tried [1,1] ; [10,10] ; [100,100] ; etc).

but that doesn’t seem to do anything. The rest of the code is exactly the same as the tutorial; I also tried moving the clamp line to the bottom of the function after reading this explanation: Help With Screen Clamping - #2 by apples but that didn’t change anything either.

I’m adding the code here in case it’s necessary:

extends Area2D

# Using the export keyword on the first variable speed allows us to set its value in the Inspector.
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.


# Called when the node enters the scene tree for the first time.
func _ready():
	screen_size = get_viewport_rect().size - Vector2(1,1)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	var velocity = Vector2.ZERO # The player's movement vector.
	if Input.is_action_pressed("move_right"):
		velocity.x += 1
	if Input.is_action_pressed("move_left"):
		velocity.x -= 1
	if Input.is_action_pressed("move_down"):
		velocity.y += 1
	if Input.is_action_pressed("move_up"):
		velocity.y -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()
		# $ returns the node at the relative path
		
	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)
	
	if velocity.x != 0:
		$AnimatedSprite2D.animation = "walk"
		$AnimatedSprite2D.flip_v = false
		$AnimatedSprite2D.flip_h = velocity.x < 0
	elif velocity.y != 0:
		$AnimatedSprite2D.animation = "up"
		$AnimatedSprite2D.flip_v = velocity.y > 0

Thanks for the help!

The reason the character goes half outisde the screen is because the position is just represented by a point, a pair of coordinates stored in a vector.
This point sits at the center of your character, so when you clamp the position you are just telling that point to not go outside the viewport. In fact If you notice when you move around the character, the sprite might go outside the screen but its center always stays inside.
If you want the entire sprite to stay on screen you have to take its size into consideration when you clamp the position.

Before we look at that though, let’s clear up how clamping actually works.
When you clamp a Vector2, you are clamping both components between the components of the min and max vectors that you pass into the clamp function.
So when you say

position = position.clamp(Vector2.ZERO, screen_size)

what you are telling Godot is:

  • restrict position.x to be between 0 and the screen width
  • restrict position.y to be between 0 and the screen height

When you tried to do

screen_size = get_viewport_rect().size - Vector2(100,100)

that actually did have an effect, although it was not what you wanted. You reduced the screen width and height by 100 pixels so the clamping function basically becomes:

  • restrict position.x to be between 0 and the screen width - 100
  • restrict position.y to be between 0 and the screen height - 100

So if you now try to go to the bottom or to the right edge of the screen, you will be stopped early.

For what you are trying to achieve, you basically want to tell Godot:

  • restrict position.x to be between half the sprite width and the screen width - half the sprite width
  • restrict position.y to be between half the sprite height and the screen height - half the sprite height

You could hardcode the sprite size value, let’s say for example your sprite is 100 x 100 pixels, then your clamp line would become:

position = position.clamp(Vector2(50, 50), screen_size - Vector2(50, 50))

That’s not optimal however, it’s much better to get the sprite size dynamically. Since you are using animated sprites you could achieve this with the follwing code:

# Get the SpriteFrames component of your AnimatedSprite2D node:
var sprite_frames = $AnimatedSprite2D.sprite_frames
# Get the first texture of the wanted animation
var texture = sprite_frames.get_frame_texture("walk", 0)
# Get frame size:
var texture_size = texture.get_size()
# multiply by the scale to get the actual size (in case you changed the scale)
var sprite_size = texture_size * $AnimatedSprite2D.get_scale()

You could put this in the ready function since the sprite size shoulldn’t change over time, and then finally your clamp line would become:

position = position.clamp(sprite_size / 2, screen_size - sprite_size / 2)

Hope this helped, have fun with Godot!

2 Likes

Thank you @pie999 !! That makes so much more sense now!

I was thinking that because my node was an Area2D it was automatically have an area! (like a square or something)

I’ve modified my code with your suggestion and now it works exactly how I wanted it! Plus now I understand things a bit better!

Thank you very much!

1 Like