How can scale my 2d character size based on their current speed?

Godot Version

4.3

Question

I’m very new to game dev, have almost no prior programming experience. I’ve been watching various tutorials, and I have been scrolling through various forums. Working on my first game and one of the core mechanics is having the players in game size (and hit box) change based on their current speed.

I don’t know if this is something that can be done but I’m hoping to make it so my character will shrink in size the faster they move. I know how to manually change size through the inspector, but don’t know how to transfer that into script and allow the script to change it as my player current speed changes.

My thought process was get character current speed
If speed is 0, then player and collision hit box is 3x3y. if speed is between 1 and 10 player size is 2x 2y. if speed is greater than 10 than their size would be 1x 1y

Eventually, I’d like to connect other player stats (like max hp, attack power, etc.) to whatever size the character is but that can be a puzzle for another day. The main goal is changing size and collision size, making it so you can only fit through tight spaces if you’re going fast enough or falling through cracks if you aren’t big enough to roll over them.

Thank you in advance for your time

1 Like

Welcome to game dev and good job putting in the work to learn everything.

You already figured out you need to programatically change the size of the player’s collision, so that’s a first step.

Go to your player script and:

extends CharacterBody2D

var speed = 100

func get_input():
	var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	velocity = input_dir * speed

func _physics_process(delta):
	get_input()
    var mycollisionshape2d: CollisionShape2d = $CollisionShape2D
    # Assuming you're using RectangleShape2D
    var myshape: RectangleShape2D = mycollisionshape2d.shape
    if speed <= 100 and speed > 0:
      myshape.size = Vector2(2,2)
    elif speed < 1:
      myshape.size = Vector2(3,3)
    elif speed > 100:
      myshape.size = Vector2(1,1)
	move_and_collide(velocity * delta)

# call this to set the new speed
func update_speed(newspeed: int):
  speed = newspeed

This will just update the collisionshape. You’ll need to adjust the numbers to fit your purpose and update the reference to $CollisionShape2D if you renamed your shape. If your player uses a sprite you can use the same logic to update that too.

I am wondering what happens when you player moves at speed 150 and has a size of (1,1), squeeses in a small space and then stops? The player would then expand in a small area and get stuck?

2 Likes

Thank you for responding! I’m playing around with it, but it’s not quite working right. I’m not getting any errors popping up but it’s probably an issue with my code elsewhere. I’ve been playing with the speed and acceleration. I’ll be able to troubleshoot that later. If I’m using CircleShape2d, would I still use Vector2(x,y)?

Currently I’ve got:
needing help 92424

I had a bit of a whoops earlier when manually changing sizes. I set it to 10,10 and when I hit play my character freaked out and kept snapping back to their starting point. I’m guessing something similar is going to happen when the player changes size while in a tight spot. I’m scheming different ways to change that, either through friction, level design, or I might add a secret and keep it as a surprise or achievement.

1 Like

You’re missing this line in the get_input function. The get_input function isn’t going to work without it.

For a CircleShape2D, you would use radius CircleShape2D — Godot Engine (stable) documentation in English instead of size.

2 Likes

I’m not receiving any error codes, but the collision shape is still staying the same size. Removed my attempt at mysprite2D just in case it I didn’t type it up correctly and potentially causing issues.

Also tried using $CollisionShape2D instead of myshape just to check. Tried myshape.size.set_radius, myshape.set_radius(), and set_radius = radius. When I set it to Print(speed) and it does look like acceleration is working. Player does change speeds.

1 Like

Try $CollisionShape2D.shape.set_radius(value)

2 Likes

Unfortunately, that did not work either. I went to test something. I added a rule that if speed is between certain points then JUMP_VELOCITY would change. example.
If speed > 1:
JUMP_VELOCITY = 0

Jump velocity didn’t change, I’m wondering if that means I’ve written something that’s overriding the speed check. I’m going to play around and look through my other code to see if I can spot my oops

1 Like

It would help if you put your code in a codeblock when you post instead of a screenshot. I actually couldn’t read your second screenshot, but I went back and read your first one just now. Your update_speed() method isn’t doing anything. You’re immediately overwriting the argument you just passed. Try changing the first line to: speed = newspeed

1 Like

I love the concept you have - look forward to seeing your demo one day.

If you solve this can you post the full solution in the discussion

2 Likes
extends CharacterBody2D

var speed = 100
const base_speed = 100
var max_speed = 500
var JUMP_VELOCITY = -1000.0
var acc = 5
@onready var sprite_2d: Sprite2D = $Sprite2D
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY
		

	if velocity.x < 1 and velocity.x > -1:
		JUMP_VELOCITY = 0
	elif speed <= 100 and speed > 1:
		JUMP_VELOCITY = -200
	elif speed <= 200 and speed > 100:
		JUMP_VELOCITY = -400
	elif speed > 200 and speed <= 300:
		JUMP_VELOCITY = -600
	elif speed > 300 and speed <= 400:
		JUMP_VELOCITY = -800
	elif speed > 400:
		JUMP_VELOCITY = -1000

	# Get the input direction and handle the movement/deceleration.
	var direction := Input.get_axis("left", "right")
	if direction:
		velocity.x = direction * speed
		if speed < max_speed:
			if Input.is_action_pressed('left'):
				speed = move_toward(-velocity.x, -max_speed, -acc)
			if Input.is_action_pressed('right'):
				speed = move_toward(velocity.x, max_speed, acc)
		if speed >= max_speed:
			speed = max_speed
	else:
		velocity.x = move_toward(velocity.x, 0, acc)
		speed = base_speed

	move_and_slide()

#Flippy bits. Move left look left, move right look right
	if Input.is_action_pressed('left'):
		#print(velocity.x)
		#print(speed)
		sprite_2d.flip_h = true

	if Input.is_action_pressed('right'):
		sprite_2d.flip_h = false 
		

#Changing Size
func get_input():
	var input_dir = Input.get_vector("left","right","jump","down")
	velocity = input_dir * speed
func _physics_proccess(delta: float) -> void:
	get_input()
	var mysprite2D: Sprite2D = $Sprite2D
	var mycollisionshape2d: CollisionShape2D = $CollisionShape2D
	var myshape: CircleShape2D = mycollisionshape2d.shape

	move_and_collide(velocity * delta)
	
func update_speed(newspeed: int):
	speed = newspeed
	if Input.is_action_pressed('left') or Input.is_action_pressed('right'):
		print(speed)
		if velocity.x < 1:
			CharacterBody2D.scale = Vector2(6,6)
			$CollisionShape2D.shape.set_radius(6)
			sprite_2d.size = Vector2(6,6)
		elif velocity.x <= 100 and velocity.x > 1:
			sprite_2d.size = Vector2(5,5)
			$CollisionShape2D.set_radius(5)
		elif velocity.x <= 200 and velocity.x > 100:
			sprite_2d.size = Vector2(4,4)
			$CollisionShape2D.set_radius(4)
		elif velocity.x > 200 and velocity.x <= 300:
			sprite_2d.size = Vector2(3,3)
			$CollisionShape2D.shape.set_radius(3)
		elif velocity.x > 300 and velocity.x <= 400:
			sprite_2d.size = Vector2(2,2)
			$CollisionShape2D.shape.set_radius(2)
		elif velocity.x > 400:
			sprite_2d.size = Vector2(1,1)
			$CollisionShape2D.shape.set_radius(1)

2 Likes
  1. According to this code, your player’s jump velocity is only increasing if your player is moving right.
  2. You have implemented _physics_proccess() twice. Your compiler should be giving you an error about that.
  3. You never appear to use mysprite2D, mycollisionshape2d, or myshape in your code.
  4. All three of those variables should be declared at the top of your file as @onready variables for two reasons. One, they are currently being declared, assigned, and destroyed 60 times a second. That could become a performance issue. Two, _physics_process can run before your object is ready, causing some gnarly race conditions. Having variables that refer to other nodes as @onready variables prevents this from happening.
  5. You are never calling your update_speed() method in this code. (I’d also call it update_size, as that is what the code is doing.)
  6. Not a huge thing, but since you update JUMP_VELOCITY after you assign it, it doesn’t change until the frame after it is modified.
  7. JUMP_VELOCITY is no longer a constant, and it will improve readability of your code if you change it to jump_velocity to be less confusing. (That’s just a coding standard thing.) I initially wondered why you were trying to assign values to a constant, as that would not work. (Likewise base_speed would be clearer as BASE_SPEED.)
2 Likes

I’ve got through as best I could and am happy to say it’s working!

I can hit play and the collision box is changing size, jumping seems to be working, acceleration and deceleration is also working. I tried a few ways to get Sprite2d but I’m unsure how I’d go about formatting it. Would it be Texture2D, sprite_2d, Sprite2D, or something else?

extends CharacterBody2D


var speed = 100
const BASE_SPEED = 100
var max_speed = 500
var jump_velocity = -100
var acc = 10
@onready var sprite_2d: Sprite2D = $Sprite2D
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D

@onready var mycollisionshape2d: CollisionShape2D = $CollisionShape2D
@onready var myshape: CircleShape2D = mycollisionshape2d.shape
@onready var mysprite2d: Sprite2D = $Sprite2D



func _physics_process(delta: float) -> void:
	update_size()
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = jump_velocity

	if speed < 1 and speed < -1:
		velocity.y = jump_velocity
	elif speed <= 100 and speed> 1:
		jump_velocity = -200
	elif speed <= 200 and speed > 100:
		jump_velocity = -400
	elif speed > 200 and speed <= 300:
		jump_velocity = -600
	elif velocity.x > 300 and speed <= 400:
		jump_velocity = -800
	elif speed > 400:
		jump_velocity = -1000

	# Get the input direction and handle the movement/deceleration.
	var direction := Input.get_axis('left', 'right')
	if direction:
		velocity.x = direction * speed
		if speed < max_speed:
			if Input.is_action_pressed('left'):
				speed = move_toward(-velocity.x, -max_speed, -acc)
			if Input.is_action_pressed('right'):
				speed = move_toward(velocity.x, max_speed, acc)
		elif speed >= max_speed:
			speed = max_speed
		elif speed < BASE_SPEED:
			speed = BASE_SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, acc)
		speed = 0
	move_and_slide()

#Flippy bits. Move left look left, move right look right
	if Input.is_action_pressed('left'):
		#print( velocity.x, jump_velocity, -speed)
		sprite_2d.flip_h = true

	if Input.is_action_pressed('right'):
		sprite_2d.flip_h = false 
		#print(velocity.x, jump_velocity, -speed)

#Changing Size- Reminder to Thank Sniper Cup and Dragonforge
func update_size():
	if Input.is_action_pressed('left') or Input.is_action_pressed('right'):
		#print(velocity.x, jump_velocity, -speed)
		#print(mycollisionshape2d.shape.radius)

		if velocity.x < 1 and velocity.x > -1:
			mycollisionshape2d.shape.set_radius(30)
			#mysprite2d.size = Vector2(6,6)- 
		elif speed <= 100 and speed > 1:
			#mysprite2d.size = Vector2(5,5)
			mycollisionshape2d.shape.set_radius(25)
		elif speed <= 200 and speed > 100:
			#mysprite2d.size = Vector2(4,4)
			mycollisionshape2d.shape.set_radius(20)
		elif speed > 200 and speed <= 300:
			#mysprite2d.size = Vector2(3,3)
			mycollisionshape2d.shape.set_radius(15)
		elif speed > 300 and speed <= 400:
			#mysprite2d.size = Vector2(2,2)
			mycollisionshape2d.shape.set_radius(10)
		elif speed > 400:
			#mysprite2d.size = Vector2(1,1)
			mycollisionshape2d.shape.set_radius(5)
	else:
		mycollisionshape2d.shape.set_radius(30)
		

2 Likes

It works! Both the player Sprite and the collisionshape change size!

extends CharacterBody2D


var speed = 100
const BASE_SPEED = 100
var max_speed = 500
var jump_velocity = -100
var acc = 10
@onready var sprite_2d: Sprite2D = $Sprite2D
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D

@onready var mycollisionshape2d: CollisionShape2D = $CollisionShape2D
@onready var myshape: CircleShape2D = mycollisionshape2d.shape
@onready var mysprite2d: Sprite2D = $Sprite2D



func _physics_process(delta: float) -> void:
	update_size()
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = jump_velocity

	if speed < 1 and speed < -1:
		velocity.y = jump_velocity
	elif speed <= 100 and speed> 1:
		jump_velocity = -200
	elif speed <= 200 and speed > 100:
		jump_velocity = -400
	elif speed > 200 and speed <= 300:
		jump_velocity = -600
	elif velocity.x > 300 and speed <= 400:
		jump_velocity = -800
	elif speed > 400:
		jump_velocity = -1000

	# Get the input direction and handle the movement/deceleration.
	var direction := Input.get_axis('left', 'right')
	if direction:
		velocity.x = direction * speed
		if speed < max_speed:
			if Input.is_action_pressed('left'):
				speed = move_toward(-velocity.x, -max_speed, -acc)
			if Input.is_action_pressed('right'):
				speed = move_toward(velocity.x, max_speed, acc)
		elif speed >= max_speed:
			speed = max_speed
		elif speed < BASE_SPEED:
			speed = BASE_SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, acc)
		speed = 0
	move_and_slide()

#Flippy bits. Move left look left, move right look right
	if Input.is_action_pressed('left'):
		#print( velocity.x, jump_velocity, -speed)
		sprite_2d.flip_h = true

	if Input.is_action_pressed('right'):
		sprite_2d.flip_h = false 
		#print(velocity.x, jump_velocity, -speed)

#Reminder to Thank SniperCup and Dragonforge from forum
#Reminder to Thank Moranth, SSci-on, TeBBRay, Necromunger, 
#Wizard-chann, FraterMomentum, Framebuffer, Alyce, rolaveric, LastElf from Discord

#Changing Size
func update_size():
	if Input.is_action_pressed('left') or Input.is_action_pressed('right'):
		#print(velocity.x, jump_velocity, -speed)
		#print(mycollisionshape2d.shape.radius)

		if velocity.x < 1 and velocity.x > -1:
			mycollisionshape2d.shape.set_radius(30)
			mysprite2d.scale = Vector2(2,2)
		elif speed <= 100 and speed > 1:
			mysprite2d.scale = Vector2(1.75,1.75)
			mycollisionshape2d.shape.set_radius(25)
		elif speed <= 200 and speed > 100:
			mysprite2d.scale = Vector2(1.5,1.5)
			mycollisionshape2d.shape.set_radius(20)
		elif speed > 200 and speed <= 300:
			mysprite2d.scale = Vector2(1.25,1.25)
			mycollisionshape2d.shape.set_radius(15)
		elif speed > 300 and speed <= 400:
			mysprite2d.scale = Vector2(.75,.75)
			mycollisionshape2d.shape.set_radius(10)
		elif speed > 400:
			mysprite2d.scale = Vector2(.5,.5)
			mycollisionshape2d.shape.set_radius(5)
	else:
		mycollisionshape2d.shape.set_radius(30)
		mysprite2d.scale = Vector2(2,2)

Thank you so much for your time and patience!

2 Likes

You’re welcome. Glad you got it working!

Did you solve this problem? I was unsure what you were asking here.

1 Like

node names are usually in PascalCase,
including the default node name based on the node type.

so to get the direct child node named Sprite2D, you can use get_node("Sprite2D") or shorthand $Sprite2D.
to get the child Sprite2D of the child Foobar, you can use get_node("Foobar/Sprite2D") or $"Foobar/Sprite2D"

1 Like

Yes, I was unsure of what node name to use but didn’t know the term I was looking for was called a node name. It ended up just being being mysprite2d for the sprite and didn’t need a secondary node name similar to how collsionshape has a secondary part called circleshape

1 Like

similar to how collsionshape has a secondary part called circleshape

and these secondary parts are called Components :slight_smile:

Thank you, I’m goinng to write that down in my personal notes. I’m also going to read up on PascalCase

1 Like