There is a problem with inconsistent speed

Godot Version

v4.1.3

Question

hello :smiling_face:

I am a newbie.

While watching YouTube and making an infinite runner game, I came up with something I wanted to ask.

I am using a MacBook and my other monitor is Samsung.

When I run the endless runner game I created on my MacBook, it plays at normal speed. But when I move it to a Samsung monitor it becomes slower.

Why does this phenomenon occur?

I attach the code for the main scene below.

If anyone can tell me about this issue, please help.

extends Node

#preload bostacles
var enemy01_scene = preload("res://scenes/enemy01.tscn")
var enemy02_scene = preload("res://scenes/enemy02.tscn")
var enemy03_scene = preload("res://scenes/enemy03.tscn")
var skyenemy_scene = preload("res://scenes/skyenemy.tscn")
# making to enemy group
var obstacle_types := [enemy01_scene, enemy02_scene, enemy03_scene]

var obstacles : Array
var skyenemy_heights := [200, 390]

# game variables
const PLAYER_START_POS := Vector2i(150, 485)
const CAM_STAR_POS := Vector2i(640, 360)
var difficulty
const MAX_DIFFICULTY : int = 2
var score : int
const SCORE_MODIFIER : int = 5 #score speed
var high_score : int
var speed : float # player
const START_SPEED : float = 5.0 # same value to modifier 
const MAX_SPEED : int = 20
const SPEED_MODIFIER : int = 5000 # to score speed and difficult apear standard
var screen_size : Vector2i
var ground_height : int # to enemy position. include main scene node
var game_running : bool # first game pressed tutorial
var last_obs

func _ready():
	screen_size = get_window().size
	ground_height = $Ground.get_node("Sprite2D").texture.get_height()
	$GameOver.get_node("Button").pressed.connect(new_game)
	new_game()
	
func new_game():
	#reset variables
	score = 0
	show_score()
	game_running = false
	get_tree().paused = false
	difficulty = 0
	
	for obs in obstacles:
		obs.queue_free()
	obstacles.clear()
	
	# reset the nodes
	$Player.position = PLAYER_START_POS
	$Player.velocity = Vector2i(0, 0)
	$Camera2D.position = CAM_STAR_POS
	$Ground.position = Vector2i(0, 0)
	
	#reset hud and game over screen
	$HUD.get_node("StartLabel").show()
	$GameOver.hide()

func _process(delta):
	if game_running: # to game start pressed tutorial
		# speed up and adjust difficulty
		speed = START_SPEED + score / SPEED_MODIFIER
		if speed > MAX_SPEED:
			speed = MAX_SPEED
		adjust_difficulty()
		
		# generate enemys
		generate_obs()
			
		# move player and camera
		$Player.position.x += speed
		$Camera2D.position.x += speed
		
		#update score
		score += speed
		show_score()
		
		# update ground position
		if $Camera2D.position.x - $Ground.position.x > screen_size.x * 1:
			$Ground.position.x += screen_size.x
		
		#remove enemys that have gone off screen
		for obs in obstacles:
			if obs.position.x < ($Camera2D.position.x - screen_size.x):
				remove_obs(obs)
		
	else:
		if Input.is_action_pressed("ui_accept"): # to game start pressed action
			game_running = true
			$HUD.get_node("StartLabel").hide()
			
func generate_obs():
	#generate ground enemys
	if obstacles.is_empty() or last_obs.position.x < score + randi_range(300, 500):
		# randi_range is emeys appear value
		var obs_type = obstacle_types[randi( )% obstacle_types.size()] # random to enemy
		var obs
		var max_obs = difficulty + 1 # for At the same time enemy amount
		for i in range(randi() % max_obs + 1): # make to dividaul enemy range
			obs = obs_type.instantiate()
			var obs_height = obs.get_node("Sprite2D").texture.get_height() # to enemy position
			var obs_scale = obs.get_node("Sprite2D").scale
			var obs_x : int = screen_size.x + score + 100 + (i * 100)#enemy x position
			var obs_y : int = screen_size.y - ground_height - (obs_height * obs_scale.y / 2) + 5
			last_obs = obs
			add_obs(obs, obs_x, obs_y)
		# additionaly ramdom chance to spawn a skyenemy
		if difficulty == MAX_DIFFICULTY: # if when game test, value is 0:
			if (randi() % 2) == 0:
				# generate skyenemy 
				obs = skyenemy_scene.instantiate()
				var obs_x : int = screen_size.x + score + 100
				var obs_y : int = skyenemy_heights[randi() % skyenemy_heights.size()]
				add_obs(obs, obs_x, obs_y) # skyenemy position
		
func add_obs(obs, x, y):
	obs.position = Vector2i(x, y)
	obs.body_entered.connect(hit_obs) # find the enemy body entered signal and connect
	add_child(obs) 
	obstacles.append(obs)

func remove_obs(obs):
	obs.queue_free()
	obstacles.erase(obs)
	
func hit_obs(body): #(body) is enemy collision. if many making enemy, use for var value
	if body.name == "Player":
		game_over()

func show_score():
	$HUD.get_node("ScoreLabel").text = "SCORE: " + str(score / SCORE_MODIFIER / 100)
	# devided by score to modifier
	# 100 is score value digit

func check_high_score():
	if score > high_score:
		high_score = score
		$HUD.get_node("HighScoreLabel").text = "HIGH SCORE: " + str(high_score / SCORE_MODIFIER / 100)

func adjust_difficulty():
	difficulty = score / SPEED_MODIFIER
	if difficulty > MAX_DIFFICULTY:
		difficulty = MAX_DIFFICULTY


func game_over():
	check_high_score()
	get_tree().paused = true
	game_running = false
	$GameOver.show()

The problem is that your movement code isn’t frame independent. If one monitor has a higher refresh rate, the character will move more times per second. If the character moves the same distance each frame, its speed will depend on the refresh rate.

The solution is to multiply speed with the delta value. Delta represents the elapsed time between frames.

$Player.position.x += speed * delta
$Camera2D.position.x += speed * delta

Now if the refresh rate is high, delta will be small and the character will move in small steps. If the refresh rate is low, delta will be larger and the character will move in larger steps.

Hello, thank you for your reply!:smiling_face:
(I kept deleting my replies by mistake.:sweat_smile:)

I tried it as you said. But when you do that, anything other than the score, moving backgrounds, players, enemies, etc. move inexplicably slowly.
So I wanted to ask what the problem was. delta value

$Player.position.x += speed * delta
$Camera2D.position.x += velocity * delta

Why doesn’t multiplying delta work?
Am I doing something wrong…? :disappointed:
I also tried changing the var value of speed in the code.
But I don’t think that’s a good way.
I want to apply delta properly but I don’t know how.

If you know of any other methods, please let me know.
Thank you.

before you applied speed in every frame but now you want to apply speed every second so you should increase the speed (20~100 time) depending on your frame rate

Thanks for your reply!
Actually I tried to follow your advice.
If you increase the speed value, the speed will be adjusted but the screen will shake.
A phenomenon similar to buffering occurs. It runs, but I don’t think it’s normal.
That’s why I said it would be good to be able to solve the problem with as much delta value as possible. :cry:

can you attach the camera as a child of the character
then you don’t have to worry about the cam position + speed

$Player.get_node(“Camera2D”)

Can I put this in _process?

Sorry I’m a newbie… I just tried what you said.
However, I received a warning that the player’s Camera2D node could not be found.:cry:

try this after you place your camera as a child of the player:

extends Node

#preload bostacles
var enemy01_scene = preload("res://scenes/enemy01.tscn")
var enemy02_scene = preload("res://scenes/enemy02.tscn")
var enemy03_scene = preload("res://scenes/enemy03.tscn")
var skyenemy_scene = preload("res://scenes/skyenemy.tscn")
# making to enemy group
var obstacle_types := [enemy01_scene, enemy02_scene, enemy03_scene]

var obstacles : Array
var skyenemy_heights := [200, 390]

# game variables
const PLAYER_START_POS := Vector2i(150, 485)
const CAM_STAR_POS := Vector2i(640, 360)
var difficulty
const MAX_DIFFICULTY : int = 2
var score : int
const SCORE_MODIFIER : int = 5 #score speed
var high_score : int
var speed : float # player
const START_SPEED : float = 5.0 # same value to modifier 
const MAX_SPEED : int = 20
const SPEED_MODIFIER : int = 5000 # to score speed and difficult apear standard
var screen_size : Vector2i
var ground_height : int # to enemy position. include main scene node
var game_running : bool # first game pressed tutorial
var last_obs

func _ready():
	screen_size = get_window().size
	ground_height = $Ground.get_node("Sprite2D").texture.get_height()
	$GameOver.get_node("Button").pressed.connect(new_game)
	new_game()
	
func new_game():
	#reset variables
	score = 0
	show_score()
	game_running = false
	get_tree().paused = false
	difficulty = 0
	
	for obs in obstacles:
		obs.queue_free()
	obstacles.clear()
	
	# reset the nodes
	$Player.position = PLAYER_START_POS
	$Player.velocity = Vector2i(0, 0)
	$Player/Camera2D.position = CAM_STAR_POS
	$Ground.position = Vector2i(0, 0)
	
	#reset hud and game over screen
	$HUD.get_node("StartLabel").show()
	$GameOver.hide()

func _process(delta):
	if game_running: # to game start pressed tutorial
		# speed up and adjust difficulty
		speed = START_SPEED + score / SPEED_MODIFIER
		if speed > MAX_SPEED:
			speed = MAX_SPEED
		adjust_difficulty()
		
		# generate enemys
		generate_obs()
			
		# move player and camera
		$Player.position.x += speed
		
		#update score
		score += speed
		show_score()
		
		# update ground position
		if $Player/Camera2D.position.x - $Ground.position.x > screen_size.x * 1:
			$Ground.position.x += screen_size.x
		
		#remove enemys that have gone off screen
		for obs in obstacles:
			if obs.position.x < ($Player/Camera2D.position.x - screen_size.x):
				remove_obs(obs)
		
	else:
		if Input.is_action_pressed("ui_accept"): # to game start pressed action
			game_running = true
			$HUD.get_node("StartLabel").hide()
			
func generate_obs():
	#generate ground enemys
	if obstacles.is_empty() or last_obs.position.x < score + randi_range(300, 500):
		# randi_range is emeys appear value
		var obs_type = obstacle_types[randi( )% obstacle_types.size()] # random to enemy
		var obs
		var max_obs = difficulty + 1 # for At the same time enemy amount
		for i in range(randi() % max_obs + 1): # make to dividaul enemy range
			obs = obs_type.instantiate()
			var obs_height = obs.get_node("Sprite2D").texture.get_height() # to enemy position
			var obs_scale = obs.get_node("Sprite2D").scale
			var obs_x : int = screen_size.x + score + 100 + (i * 100)#enemy x position
			var obs_y : int = screen_size.y - ground_height - (obs_height * obs_scale.y / 2) + 5
			last_obs = obs
			add_obs(obs, obs_x, obs_y)
		# additionaly ramdom chance to spawn a skyenemy
		if difficulty == MAX_DIFFICULTY: # if when game test, value is 0:
			if (randi() % 2) == 0:
				# generate skyenemy 
				obs = skyenemy_scene.instantiate()
				var obs_x : int = screen_size.x + score + 100
				var obs_y : int = skyenemy_heights[randi() % skyenemy_heights.size()]
				add_obs(obs, obs_x, obs_y) # skyenemy position
		
func add_obs(obs, x, y):
	obs.position = Vector2i(x, y)
	obs.body_entered.connect(hit_obs) # find the enemy body entered signal and connect
	add_child(obs) 
	obstacles.append(obs)

func remove_obs(obs):
	obs.queue_free()
	obstacles.erase(obs)
	
func hit_obs(body): #(body) is enemy collision. if many making enemy, use for var value
	if body.name == "Player":
		game_over()

func show_score():
	$HUD.get_node("ScoreLabel").text = "SCORE: " + str(score / SCORE_MODIFIER / 100)
	# devided by score to modifier
	# 100 is score value digit

func check_high_score():
	if score > high_score:
		high_score = score
		$HUD.get_node("HighScoreLabel").text = "HIGH SCORE: " + str(high_score / SCORE_MODIFIER / 100)

func adjust_difficulty():
	difficulty = score / SPEED_MODIFIER
	if difficulty > MAX_DIFFICULTY:
		difficulty = MAX_DIFFICULTY


func game_over():
	check_high_score()
	get_tree().paused = true
	game_running = false
	$GameOver.show()

Sorry… I tried putting the camera as a child of the player as you suggested and also applied the code.
However, the area of ​​the screen where the camera is projected is completely outside the area, so I am trying to figure it out, but I am not able to understand it very well.

Thank you for your kind notice. I’m sorry for my inability to repay your kindness.

Does putting the camera as a child fix the shakiness? Do you at least see the ground, or is the camera showing empty space? Because of how the camera works, when you add it as a child, the camera centers on 0, 0 as opposed to normally, when the camera’s top left corner is 0, 0. Also, adding a child to a node and using the position attribute changes its position relative to the parent rather than its global position. The issue might be with the CAM_STAR_POS variable. In the new_game() function, you set the camera’s position to (640, 360). When the camera wasn’t a child of the Player, setting its position would set its position relative to the world. Now, it’s being set to the position relative to the Player. I don’t have a guaranteed solution, but in the new_game() function, you could try $Player/Camera2D.global_position = CAM_STAR_POS, instead of position.

P.S. Could you send a picture of your node tree and the 2D view of the main scene?

LAST THING: Anywhere in the code there is $Player/Camera2D NEEDS to be$“Player/Camera” (the quotes are important as without them, you might get an error).

Thanks for your reply!
I modified the script as you mentioned and added Camera2D as a child of the player.
However, the location of the ground changes and the camera follows the player when he jumps, so the enemy is not visible properly.

I tried to make a simple mobile game by watching lectures on YouTube and studying, but I felt like I was lacking a lot.
I just wanted to develop a simple game into something wittier, but this makes me feel frustrated.:sweat_smile: haha

I will attach the node tree and game execution scene you mentioned, as well as the code!

extends Node

#preload bostacles
var enemy01_scene = preload("res://scenes/enemy01.tscn")
var enemy02_scene = preload("res://scenes/enemy02.tscn")
var enemy03_scene = preload("res://scenes/enemy03.tscn")
var skyenemy_scene = preload("res://scenes/skyenemy.tscn")
# making to enemy group
var obstacle_types := [enemy01_scene, enemy02_scene, enemy03_scene]

var obstacles : Array
var skyenemy_heights := [200, 390]

# delivery variables
var food_delivery : Array
var food_delivery_start := Vector2i(200, 485)
var delivery_speed
const DELIVERY_SPEED : float = 1000.0
const DELIVERY_MAX_SPEED : int = 1500
var delivery_running : bool


# game variables
const PLAYER_START_POS := Vector2i(150, 485)
const CAM_STAR_POS := Vector2i(640, 360)
var difficulty
const MAX_DIFFICULTY : int = 200
var score : int
const SCORE_MODIFIER : int = 500 #score speed
var high_score : int
var speed : float # player
const START_SPEED : float = 500.0 # same value to modifier 
const MAX_SPEED : int = 1500 # to score speed and difficult apear standard
const SPEED_MODIFIER : int = 20000 # to score speed and difficult apear standard
var screen_size : Vector2i
var ground_height : int # to enemy position. include main scene node
var game_running : bool # first game pressed tutorial
var last_obs

func _ready():
	screen_size = get_window().size
	ground_height = $Ground.get_node("Sprite2D").texture.get_height()
	$GameOver.get_node("Button").pressed.connect(new_game)
	$Delivery.hide()
	new_game()
	
func new_game():
	#reset variables
	score = 0
	show_score()
	game_running = false
	get_tree().paused = false
	difficulty = 0
	
	for obs in obstacles:
		obs.queue_free()
	obstacles.clear()
	
	#for fd in food_delivery:
	#	fd.queue_free()
	
	
	# reset the nodes
	$Player.position = PLAYER_START_POS
	$Player.velocity = Vector2i(0, 0)
	$"Player/Camera2D".global_position = CAM_STAR_POS
	$Ground.position = Vector2i(0, 0)	
	
	# food delivery animation
	food_del()
	$Delivery.show()
	
	#reset hud and game over screen
	$HUD.get_node("StartLabel").show()
	$GameOver.hide()

func _process(delta):
	if game_running: # to game start pressed tutorial
		# speed up and adjust difficulty
		speed = START_SPEED + score / SPEED_MODIFIER
		if speed > MAX_SPEED:
			speed = MAX_SPEED
		adjust_difficulty()
		
		# food delivery animation
		food_del()
		$Delivery.show()
		
		# generate enemys
		generate_obs()
			
		# move player and camera
		$Player.position.x += speed * delta
		$Camera2D.position.x += speed * delta
		
		$Delivery.position.x += delivery_speed * delta
		
		
		
		#update score
		score += speed 
		show_score()
		
		# update ground position
		if $Camera2D.position.x - $Ground.position.x > screen_size.x * 1:
			$Ground.position.x += screen_size.x
		
		#remove enemys that have gone off screen
		for obs in obstacles:
			if obs.position.x < ($Camera2D.position.x - screen_size.x):
				remove_obs(obs)
		
	else:
		if Input.is_action_pressed("ui_accept"): # to game start pressed action
			game_running = true
			$HUD.get_node("StartLabel").hide()


func food_del():
	if game_running: # to game start pressed tutorial
		# speed up and adjust difficulty
		delivery_speed = DELIVERY_SPEED + score / 20
		if delivery_speed > DELIVERY_MAX_SPEED:
			delivery_speed = DELIVERY_MAX_SPEED
			

#func remove_food_del(fd):
#	fd.queue_free()
	

func generate_obs():
	#generate ground enemys
	if obstacles.is_empty() or last_obs.position.x < score + randi_range(300, 500):
		# randi_range is emeys appear value
		var obs_type = obstacle_types[randi( )% obstacle_types.size()] # random to enemy
		var obs
		var max_obs = difficulty + 1 # for At the same time enemy amount
		for i in range(randi() % max_obs + 1): # make to dividaul enemy range
			obs = obs_type.instantiate()
			var obs_height = obs.get_node("Sprite2D").texture.get_height() # to enemy position
			var obs_scale = obs.get_node("Sprite2D").scale
			var obs_x : int = screen_size.x + score + 100 + (i * 100)#enemy x position
			var obs_y : int = screen_size.y - ground_height - (obs_height * obs_scale.y / 2) + 5
			last_obs = obs
			add_obs(obs, obs_x, obs_y)
		# additionaly ramdom chance to spawn a skyenemy
		if difficulty == MAX_DIFFICULTY: # if when game test, value is 0:
			if (randi() % 2) == 0:
				# generate skyenemy 
				obs = skyenemy_scene.instantiate()
				var obs_x : int = screen_size.x + score + 100
				var obs_y : int = skyenemy_heights[randi() % skyenemy_heights.size()]
				add_obs(obs, obs_x, obs_y) # skyenemy position
		
func add_obs(obs, x, y):
	obs.position = Vector2i(x, y)
	obs.body_entered.connect(hit_obs) # find the enemy body entered signal and connect
	add_child(obs) 
	obstacles.append(obs)

func remove_obs(obs):
	obs.queue_free()
	obstacles.erase(obs)
	

	
	
func hit_obs(body): #(body) is enemy collision. if many making enemy, use for var value
	if body.name == "Player":
		game_over()

func show_score():
	$HUD.get_node("ScoreLabel").text = "SCORE: " + str(score / SCORE_MODIFIER / 100)
	# devided by score to modifier
	# 100 is score value digit

func check_high_score():
	if score > high_score:
		high_score = score
		$HUD.get_node("HighScoreLabel").text = "HIGH SCORE: " + str(high_score / SCORE_MODIFIER / 100)

func adjust_difficulty():
	difficulty = score / SPEED_MODIFIER
	if difficulty > MAX_DIFFICULTY:
		difficulty = MAX_DIFFICULTY


func game_over():
	check_high_score()
	get_tree().paused = true
	game_running = false
	$GameOver.show()

스크린샷 2024-04-16 오후 5.26.26

sorry…
Since I’m a new member of the Godot forum, they say I can only attach one piece of media… oops.

To fix the jump issue, you could set the camera’s global_position.y to the desired y of the camera. It might also be helpful to see a video of what is happening. Not required, but if you can, that would be great.

Thank you for answer!
I will try harder. :laughing: