How do i make a loop that plays frame by frame

Godot Version

4.3

Question

so if you make a for loop like this:

for n in 100:
frame += 1
print(frame)

all the 100 ticks play in one frame and i want that the 1 tick will play at 1 frame and the next tick at the next frame. i tried this first and this is how it looks:

for n in 100:
frame += 1
print(frame)
await get_tree().create_timer(0).timeout

with this code, it worked but sometimes the game crashes and says:

cannot call create_timer() on a null value

what i now is that the problem is at the
await get_tree().create_timer(0).timeout but i dont know how to fix it and its a really important thing at my game. BTW im using for loops

if you know the answer pls reply, i will really appreciate it :smiley:

If you want to do a operation frame paced you should use the _process and _physics_process functions. _process is called for every frame generated and _physics_process is called in fixed step of 60 times per second.

If you call a big for loop in the main thread you’ll stall your game while the for loop runs. What you need to do with this loop? Some explanation can help elaborate a proper solution.

so the player goes room by room,the camera is static, if you exit the room the camera goes to that room,now i added a save feature,that saves the game everytime you enter a room.i did that by putting areas by every entrance/exit of the rooms,once you quit the game and go play it again,you will spawn on the last room you where,but the camera must know that you are in that room so it goes there with this code:

extends Camera2D

@onready var player: CharacterBody2D = $“…/Player”

Called when the node enters the scene tree for the first time.

func _ready() → void:
start()

Called every frame. ‘delta’ is the elapsed time since the previous frame.

func _process(delta: float) → void:
pass

func camera_panning() → void:
position = player.position
var x = floor(position.x / 960) * 960
var y = floor(position.y / 540) * 540

position = Vector2(x, y)

var tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
tween.tween_property(self, "position", Vector2(x, y), 0.15)

func start() → void:
for c in 2000:
position = player.position
var x = floor(position.x / 960) * 960
var y = floor(position.y / 540) * 540

	position = Vector2(x, y)
	var tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
	tween.tween_property(self, "position", Vector2(x, y), 0)
	await get_tree().create_timer(0).timeout

and here is the save system(main) and player code:

###main.gd

extends Node2D

@onready var player: CharacterBody2D = $Player

var config = ConfigFile.new()

Called when the node enters the scene tree for the first time.

func _ready() → void:
var saved_data = config.load(“res://savegame.cfg”)
if saved_data == OK:
$Player.position.x = config.get_value(“Player”, “x”)
$Player.position.y = config.get_value(“Player”, “y”)

Called every frame. ‘delta’ is the elapsed time since the previous frame.

func _process(delta: float) → void:
pass

func _input(event: InputEvent) → void:
if Input.is_action_just_pressed(“pause”):
get_tree().quit()
if Input.is_action_just_pressed(“restart”):
restart()

func restart() → void:
get_tree().reload_current_scene()

func save() → void:
config.set_value(“Player”, “x”, round($Player.position.x))
config.set_value(“Player”, “y”, round($Player.position.y))
config.save(“res://savegame.cfg”)

###player.gd

extends CharacterBody2D

const SPEED = 300
const DASH_SPEED = 700
const JUMP_VELOCITY = -350.0
const wall_jump = 1000

var alive = true
var dash_counter = 0
var dash_max = 2
var dashing = false
var can_dash = true
var jump_counter = 0
var gravity = 980
@export var jump_max = 2

@onready var anim: AnimatedSprite2D = $AnimatedSprite2D
@onready var ray_cast_2d: RayCast2D = $RayCast2D

func _ready() → void:
top_level = true
anim.show()

func _physics_process(delta: float) → void:
var direction := Input.get_axis(“left”, “right”)
if alive:
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
if velocity.y < 0:
anim.play(“Jump”)
if velocity.y > 0:
anim.play(“Fall”)
if ray_cast_2d.is_colliding():
anim.play(“Wall_Slide”)
if velocity.y > 0:
gravity = 200
else:
gravity = 1500

		else:
			gravity = 980
	else:
		jump_counter = 0
		dash_counter = 0

	# Handle jump.
	if Input.is_action_just_pressed("jump") and jump_counter < jump_max:
		jump_counter += 1
		velocity.y = JUMP_VELOCITY
		if ray_cast_2d.is_colliding():
			if anim.flip_h == true:
				velocity.x = 20
			else:
				velocity.x = -20
			
	if Input.is_action_just_pressed("dash") and can_dash and !is_on_floor() and dash_counter < dash_max:
		anim.play("Dash")
		if direction == 0:
			velocity.y = JUMP_VELOCITY - 170
		$dashParticles.emitting = true
		dashing = true
		can_dash = false
		dash_counter += 1
		await get_tree().create_timer(0.15).timeout
		can_dash = true
		dashing = false

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	if is_on_floor():
		if direction == 0:
			anim.play("Idle")
		if direction > 0 or direction < 0:
			anim.play("Run")
	if ray_cast_2d.is_colliding():
		jump_counter = 0
		dash_counter = 0
		
	if direction:
		if dashing:
			velocity.x = direction * DASH_SPEED
		else:
			velocity.x = direction * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
	
	move_and_slide()

func _input(event: InputEvent) → void:
if alive:
if Input.is_action_just_pressed(“left”):
ray_cast_2d.target_position = Vector2(-3, 0)
anim.flip_h = true
if Input.is_action_just_pressed(“right”):
ray_cast_2d.target_position = Vector2(3, 0)
anim.flip_h = false

func die() → void:
alive = false
anim.hide()
$CPUParticles2D.emitting = true
await get_tree().create_timer(1.3).timeout
restart()

func restart() → void:
$“…”.restart()

func _on_visible_on_screen_notifier_2d_screen_exited() → void:
for c in Engine.get_frames_per_second():
$“…/Camera2D”.camera_panning()
await get_tree().create_timer(0).timeout

func _on_hurt_box_body_entered(body: Node2D) → void:
die()

func _on_save_box_body_entered(body: Node2D) → void:
$“…”.save()

Just to add onto that, you can also change how often the _physics_process runs in the project settings.

yeah but i want it to happen on the first 100-2000 frames and not the hole time

If you need something happens only for a x amount of frames you can use _process function with a counter, example:

var frame_count := 0

func _ready() -> void:
	set_process(false)


func start_operation() -> void:
	frame_count = 0
	set_process(true)


func _process(delta) -> void:
	if frame_count <= 2000:
		# Do your code here
		
		frame_count += 1

	else:
		set_process(false)

thank you a lot! i really appreciate it!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.