How to resize the player's ship?

Hi, I’m using Godot 4.3 and I’m updating my shootem up but I’m having a problem. I’d like to resize the elements on the screen. I’m testing my ship first but I’m not sure how to resize it all while keeping its base speed and position and limits so that it doesn’t go out of the screen. I’ve tested several scripts but without much success. When I resize the screen manually the ship changes position in x.


Test code for ship resizing.

extends Area2D

@export var speed: float = 300.0
@export var laser_scene: PackedScene
@export var laser_sound: AudioStream = preload("res://musiqueetson/amiral3000lasersound.wav")
@export var boom_texture: Texture = preload("res://alienminigames/boomtexture.png")

var can_fire: bool = true
var controls_enabled: bool = true   # Contrôle local pour le traitement (mais on utilisera aussi le flag "frozen")
var frozen: bool = false            # Nouvel indicateur pour forcer l'arrêt complet du traitement
var initial_y: float = 0.0          # Position Y initiale

# Variables pour la gestion du redimensionnement
var base_resolution := Vector2(1280, 720)  # Résolution d'origine
var base_scale := Vector2(1.0, 1.0)        # Échelle d'origine du sprite
var screen_size := Vector2.ZERO            # Taille actuelle de l'écran
var left_boundary: float = 60              # Limite gauche à la résolution de base
var right_boundary: float = 1220           # Limite droite à la résolution de base

# Gestion des vies
var player_lives: int = 3
var life_sprites: Array = []

# Variables d'explosion
var is_exploding: bool = false
var original_texture: Texture      # Pour sauvegarder le sprite normal
var original_scale: Vector2        # Pour sauvegarder l'échelle d'origine

func _ready() -> void:
	# Tenter d'utiliser la méthode générique pour définir le mode pause (valeur 1 = STOP)
	set("pause_mode", 1)
	
	initial_y = position.y
	if not laser_scene:
		laser_scene = preload("res://lasertest.tscn")
	
	var level_scene = get_tree().current_scene
	life_sprites = [
		level_scene.get_node("vievaisseau1"),
		level_scene.get_node("vievaisseau2"),
		level_scene.get_node("vievaisseau3")
	]
	
	original_texture = $Sprite2D.texture
	original_scale = $Sprite2D.scale
	base_scale = Vector2($Sprite2D.scale.x, $Sprite2D.scale.y)  # Mémoriser l'échelle de base

	connect("area_entered", Callable(self, "_on_area_entered"))
	
	# Enregistrer la taille initiale de l'écran
	screen_size = get_viewport_rect().size
	
	# Se connecter au signal de redimensionnement de la fenêtre
	get_tree().root.connect("size_changed", Callable(self, "_on_window_resize"))
	
	# Adapter le vaisseau à la résolution initiale
	adjust_to_screen_size()
	
	print("Vaisseau prêt. Contrôles actifs =", controls_enabled, ", frozen =", frozen)

func _physics_process(delta: float) -> void:
	# Si le vaisseau est figé, on ne fait rien.
	if frozen:
		return

	if not is_exploding and controls_enabled:
		var velocity: Vector2 = Vector2.ZERO
		if Input.is_action_pressed("ui_left"):
			velocity.x -= 1
		if Input.is_action_pressed("ui_right"):
			velocity.x += 1
		if velocity.length() > 0:
			# Adapter la vitesse à la résolution actuelle
			var adjusted_speed = speed * (screen_size.x / base_resolution.x)
			velocity = velocity.normalized() * adjusted_speed
		position.x += velocity.x * delta
		
		# Adapter les limites à la résolution actuelle
		var scaled_left = left_boundary * (screen_size.x / base_resolution.x)
		var scaled_right = right_boundary * (screen_size.x / base_resolution.x)
		position.x = clamp(position.x, scaled_left, scaled_right)
		
		# Maintenir la position Y fixe en bas de l'écran
		position.y = initial_y
		
		if Input.is_action_just_pressed("fire") and can_fire and controls_enabled:
			fire_laser()
	else:
		# Débogage pour vérifier l'état
		print("Vaisseau inactif: is_exploding =", is_exploding, ", controls_enabled =", controls_enabled)

func _on_window_resize() -> void:
	screen_size = get_viewport_rect().size
	adjust_to_screen_size()

func adjust_to_screen_size() -> void:
	# Calculer le ratio par rapport à la résolution de base
	var scale_ratio_x = screen_size.x / base_resolution.x
	var scale_ratio_y = screen_size.y / base_resolution.y
	
	# Appliquer l'échelle au sprite
	$Sprite2D.scale = base_scale * min(scale_ratio_x, scale_ratio_y)
	
	# IMPORTANT: Forcer la position Y en bas de l'écran (avec un décalage fixe)
	# On suppose que le vaisseau est à ~90% de la hauteur de l'écran dans la conception originale
	position.y = screen_size.y * 0.9
	initial_y = position.y
	
	# Redimensionner la collision si nécessaire
	if has_node("CollisionShape2D"):
		$CollisionShape2D.scale = Vector2(scale_ratio_x, scale_ratio_y)
	
	# Ajuster la position du point d'émission des lasers
	if has_node("Projectile"):
		# Repositionner le point d'émission des lasers proportionnellement
		var original_offset = $Projectile.position.y / base_resolution.y
		$Projectile.position.y = original_offset * screen_size.y

func fire_laser() -> void:
	var laser_instance = laser_scene.instantiate()
	laser_instance.global_position = $Projectile.global_position
	
	# Ajuster l'échelle du laser en fonction de la résolution
	var scale_ratio = min(screen_size.x / base_resolution.x, screen_size.y / base_resolution.y)
	if laser_instance.has_node("Sprite2D"):
		laser_instance.get_node("Sprite2D").scale *= scale_ratio
	if laser_instance.has_node("CollisionShape2D"):
		laser_instance.get_node("CollisionShape2D").scale *= scale_ratio
	
	get_tree().current_scene.add_child(laser_instance)
	laser_instance.add_to_group("player_laser")
	can_fire = false
	laser_instance.connect("laser_removed", Callable(self, "_on_laser_removed"))
	
	if laser_sound:
		$AudioStreamPlayer.stream = laser_sound
		print("Lecture du son du laser")
		$AudioStreamPlayer.play()
	else:
		print("Aucun son de laser assigné!")

func _on_laser_removed() -> void:
	can_fire = true

func _on_area_entered(area: Area2D) -> void:
	if area.is_in_group("enemy_laser"):
		if not is_exploding:
			take_damage()
		area.queue_free()

func take_damage() -> void:
	if is_exploding:
		return
	if player_lives > 0:
		player_lives -= 1
		life_sprites[player_lives].hide()
		print("Joueur touché ! Vies restantes :", player_lives)
		start_explosion()
	else:
		die()

func start_explosion() -> void:
	is_exploding = true
	$Sprite2D.texture = boom_texture
	$Sprite2D.scale = original_scale * 0.7 * (screen_size.x / base_resolution.x)
	$ExplosionPlayer.play()
	if not $ExplosionPlayer.is_connected("finished", Callable(self, "_on_explosion_finished")):
		$ExplosionPlayer.connect("finished", Callable(self, "_on_explosion_finished"))

func _on_explosion_finished() -> void:
	if player_lives == 0:
		die()
	else:
		$Sprite2D.texture = original_texture
		# Appliquer l'échelle adaptée à la résolution actuelle
		var scale_ratio = min(screen_size.x / base_resolution.x, screen_size.y / base_resolution.y)
		$Sprite2D.scale = base_scale * scale_ratio
		is_exploding = false

func die() -> void:
	get_tree().call_deferred("change_scene_to_file", "res://gameover.tscn")

# Fonction destinée à désactiver les contrôles du vaisseau.
# Cette fonction doit être appelée par le script de fin de niveau.
func disable_controls() -> void:
	print("disable_controls() appelée - le vaisseau doit être figé.")
	controls_enabled = false
	can_fire = false
	frozen = true   # On active le flag pour stopper complètement _physics_process
	# On désactive également les traitements de ce node (bien que le flag devrait suffire)
	set_process(false)
	set_physics_process(false)
	set_process_input(false)
	disable_all_lasers()

func disable_all_lasers() -> void:
	for laser in get_tree().get_nodes_in_group("player_laser"):
		laser.queue_free()
	for laser in get_tree().get_nodes_in_group("enemy_laser"):
		laser.queue_free()

The original code of the player’s ship without resizing

extends Area2D

@export var speed: float = 300.0
@export var laser_scene: PackedScene
@export var laser_sound: AudioStream = preload("res://musiqueetson/amiral3000lasersound.wav")
@export var boom_texture: Texture = preload("res://alienminigames/boomtexture.png")

var can_fire: bool = true
var controls_enabled: bool = true   # Contrôle local pour le traitement (mais on utilisera aussi le flag "frozen")
var frozen: bool = false            # Nouvel indicateur pour forcer l'arrêt complet du traitement
var initial_y: float = 0.0          # Position Y initiale

# Gestion des vies
var player_lives: int = 3
var life_sprites: Array = []

# Variables d'explosion
var is_exploding: bool = false
var original_texture: Texture      # Pour sauvegarder le sprite normal
var original_scale: Vector2        # Pour sauvegarder l'échelle d'origine

func _ready() -> void:
	# Tenter d'utiliser la méthode générique pour définir le mode pause (valeur 1 = STOP)
	set("pause_mode", 1)
	
	initial_y = position.y
	if not laser_scene:
		laser_scene = preload("res://lasertest.tscn")
	
	var level_scene = get_tree().current_scene
	life_sprites = [
		level_scene.get_node("vievaisseau1"),
		level_scene.get_node("vievaisseau2"),
		level_scene.get_node("vievaisseau3")
	]
	
	original_texture = $Sprite2D.texture
	original_scale = $Sprite2D.scale

	connect("area_entered", Callable(self, "_on_area_entered"))
	
	print("Vaisseau prêt. Contrôles actifs =", controls_enabled, ", frozen =", frozen)

func _physics_process(delta: float) -> void:
	# Si le vaisseau est figé, on ne fait rien.
	if frozen:
		# Pour debug, on affiche une fois
		# print("Vaisseau figé (frozen = true)")
		return

	if not is_exploding and controls_enabled:
		var velocity: Vector2 = Vector2.ZERO
		if Input.is_action_pressed("ui_left"):
			velocity.x -= 1
		if Input.is_action_pressed("ui_right"):
			velocity.x += 1
		if velocity.length() > 0:
			velocity = velocity.normalized() * speed
		position.x += velocity.x * delta
		position.x = clamp(position.x, 60, 1220)
		position.y = initial_y
		if Input.is_action_just_pressed("fire") and can_fire and controls_enabled:
			fire_laser()
	else:
		# Débogage pour vérifier l'état
		print("Vaisseau inactif: is_exploding =", is_exploding, ", controls_enabled =", controls_enabled)

func fire_laser() -> void:
	var laser_instance = laser_scene.instantiate()
	laser_instance.global_position = $Projectile.global_position
	get_tree().current_scene.add_child(laser_instance)
	laser_instance.add_to_group("player_laser")
	can_fire = false
	laser_instance.connect("laser_removed", Callable(self, "_on_laser_removed"))
	
	if laser_sound:
		$AudioStreamPlayer.stream = laser_sound
		print("Lecture du son du laser")
		$AudioStreamPlayer.play()
	else:
		print("Aucun son de laser assigné!")

func _on_laser_removed() -> void:
	can_fire = true

func _on_area_entered(area: Area2D) -> void:
	if area.is_in_group("enemy_laser"):
		if not is_exploding:
			take_damage()
		area.queue_free()

func take_damage() -> void:
	if is_exploding:
		return
	if player_lives > 0:
		player_lives -= 1
		life_sprites[player_lives].hide()
		print("Joueur touché ! Vies restantes :", player_lives)
		start_explosion()
	else:
		die()

func start_explosion() -> void:
	is_exploding = true
	$Sprite2D.texture = boom_texture
	$Sprite2D.scale = original_scale * 0.7
	$ExplosionPlayer.play()
	if not $ExplosionPlayer.is_connected("finished", Callable(self, "_on_explosion_finished")):
		$ExplosionPlayer.connect("finished", Callable(self, "_on_explosion_finished"))

func _on_explosion_finished() -> void:
	if player_lives == 0:
		die()
	else:
		$Sprite2D.texture = original_texture
		$Sprite2D.scale = original_scale
		is_exploding = false

func die() -> void:
	get_tree().call_deferred("change_scene_to_file", "res://gameover.tscn")

# Fonction destinée à désactiver les contrôles du vaisseau.
# Cette fonction doit être appelée par le script de fin de niveau.
func disable_controls() -> void:
	print("disable_controls() appelée - le vaisseau doit être figé.")
	controls_enabled = false
	can_fire = false
	frozen = true   # On active le flag pour stopper complètement _physics_process
	# On désactive également les traitements de ce node (bien que le flag devrait suffire)
	set_process(false)
	set_physics_process(false)
	set_process_input(false)
	disable_all_lasers()

func disable_all_lasers() -> void:
	for laser in get_tree().get_nodes_in_group("player_laser"):
		laser.queue_free()
	for laser in get_tree().get_nodes_in_group("enemy_laser"):
		laser.queue_free()

Basically, it sounds like you want to define how window stretching affect the game scene.

Head over to the editor’s Project Settings under the Project tab, and find Display -> Window in the left menu, where you can see a Stretch section contains a Mode setting.

We usually express it like this: find the UI Project -> Project Settings, then find the setting display/window/stretch/mode.

I’ve already tried with the stretch settings but there are still problems. The limits of the player’s ship and the enemy formation are no longer the same when I enlarge and reduce the screen and it gives a strange result when I decrease the resolution horizontally. Also the Sprite2D of the background shrinks when I decrease the screen resolution and does not take all the screen resolution when the game window is at its normal resolution (1280X720) or when I enlarge the screen in a higher resolution.