Stop player from moving when interacting with object

Godot Version

4.3.stable

Question

Hi, still on my first week with godot. I’ve a basic interaction script between player and door. I’m trying to pause the player’s movement once the input for interaction is triggered, then resume movement if they select “No”.

How can I do this? My code for the door script is below. I’ve tried something like set_physics_process(false) in the If statement but I don’t think I’m using it right.

Help is appreciated, thank you.

extends StaticBody2D

var near_door = false
var want_to_exit = false
var player_talking = false

@onready var leaving = $Leaving

# Called when the node enters the scene tree for the first time.
func _ready():
	leaving.visible = false


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if near_door && Input.is_action_just_pressed("interact"):
		leaving.visible = true
	if Input.is_action_just_pressed("yes"):
		get_tree().change_scene_to_file("res://Scenes/main.tscn")
	elif Input.is_action_just_pressed("no"):
		leaving.visible = false
		print("player does not want to leave yet")
		


func _on_exit_body_entered(body):
	if body is CharacterBody2D: 
		near_door = true
		print("player is at the door")

func _on_exit_body_exited(body):
	if body is CharacterBody2D:
		near_door = false
		print("player walked away from the doors")

player code

extends CharacterBody2D
class_name Player

const SPEED = 600

@onready var player = self
@onready var _animated_sprite = $AnimatedSprite2D

func _ready():
	add_to_group("player")

# make character move
func _physics_process(delta):
	var direction= Input.get_vector("move_left","move_right","move_up","move_down")
	velocity = direction * SPEED
	move_and_slide()

	if Input.is_action_pressed("move_left"):
		_animated_sprite.play("move_left")
	elif Input.is_action_pressed("move_down"):
		_animated_sprite.play("move_down")
	elif Input.is_action_pressed("move_up"):
		_animated_sprite.play("move_up")
	elif Input.is_action_pressed("move_right"):
		_animated_sprite.play("move_right")

	

You need to show the player code

I’ve added the player code to the original post, thank you.

You can use the set_physics_process to do that, but you need to call it in the player script (that only stops the script that called this from receive the _phyiscs_process call, not the entire game). Also you need to correct your logic to show the leaving dialogue, from what i see now, if you press the action yes, even if not inside the desired area, your code will change for the next scene:

extends StaticBody2D

var near_door = false
var want_to_exit = false
var player_talking = false

# You need to store the player reference when
# he enters the area
var player_reference

@onready var leaving = $Leaving

# Called when the node enters the scene tree for the first time.
func _ready():
	leaving.visible = false


# No need to use _process for check that, use the 
# _unhandled_input callback instead
func _unhandled_input(event: InputEvent):
	# If player is not close from the door you don't need to 
	# check anything, return early instead
	if near_door == false:
		return

	if Input.is_action_just_pressed("interact"):
		leaving.visible = true

	# Only leave to next scene if leaving is already visible and 
	# the correct input is received
	elif leaving.visible and Input.is_action_just_pressed("yes"):
		get_tree().change_scene_to_file("res://Scenes/main.tscn")

	elif leaving.visible and Input.is_action_just_pressed("no"):
		leaving.visible = false

		# Now you use the player reference to reactivate
		# the _physics_process
		player_reference.set_physics_process(true)

		print("player does not want to leave yet")
		


func _on_exit_body_entered(body):
	if body is CharacterBody2D: 
		# You need to set the player _physics_process to false
		body.set_physics_process(false)

		# Also need a reference of player script for later
		player_reference = body

		near_door = true
		print("player is at the door")

func _on_exit_body_exited(body):
	if body is CharacterBody2D:
		near_door = false
		print("player walked away from the doors")
1 Like

Thank you for catching the logic error, I did not notice you could move to the next scene when pressing Y outside of range.

I tried the corrections in your script and it works great, but I also have a few questions for the sake of learning. Would be really grateful if you could explain them to me.

How does player_reference.set_physics_process(true) work? I see that body is stored into player_reference. My understanding is that the signal will trigger as long as it is a CharacterBody2D node inside. Can I understand it as set_physics_process is applied to any CharacterBody2D object that walks into the area instead of referencing the actual player.gd script? To test this I created another character using CharacterBody2D and it also triggers the player_reference.set_physics_process(false) when walking into the area.

What’s the difference in using _unhandled_input(event:InputEvent) vs _process?

I see that the player will stop moving as soon as they enter the area of the door. I actually want the player to pause only when they interact with the door at this stage:

	if Input.is_action_just_pressed("interact"):
		leaving.visible = true
		player_reference.set_physics_process(false)

When I do it this way, my game will crash. I’ve fixed that by writing player_reference.set_physics_process(false) before leaving.visible = true. I’m wondering what is the logic behind the order?

Of course, that’s the thing i love to see, people wanting to true learn the things, not just copy-paste the answer.


Yes, you’re right, any CharacterBody2D that enters this area will trigger the signal and call the body_set_physics_process(false) because the checking only looks for the node type (the if body is CharacterBody2D). If you have other similar bodies or want to do a more reliable code you can use groups for that, you already put the player in the “player” group, so you can use:

func _on_exit_body_entered(body):
	# Instead of check the node type, you can check if the node
	# is part of the desired group, so only the nodes you manually
	# added to "player" group will trigger that
	if body.is_in_group("player"): 
		near_door = true
		print("player is at the door")

func _on_exit_body_exited(body):
	if body.is_in_group("player"):
		near_door = false
		print("player walked away from the doors")

_process callback is called every time your machine renders a frame, so if your machine is playing at 60 frames that will be called 60 times per second, if is playing at 120 frames, 120 times per second and go on. Is the place to update visual objects like something that needs to be updated for the mouse position.

_unhandled_input callback is called when two conditions met: Player make an input (press/release a key/mouse button/joypad button, move the mouse, touch/release the screen or drag in touchscreen devices) and no other node handle the input before (in _input or _gui_input). Is the place to deal with things that happens with a player input.

Is not that you can’t use _process for that, is more about have the things in the right place.


That’s interesting, i can’t see why this is happening as is, which error your editor gives for you with the old order?

Thank you for all of your replies, I’ve learned a lot!

As for the last problem, it went away now and I haven’t been able to replicate it even by switching back to the old order. But the error it gave previously was something like Nonexistent function 'set_physics_process' in base ‘Nil’.

That was i suspected but is impossible because player_reference is set before near_door is setted to true, so the code that could triggers that can’t be called before the player_reference be set, maybe you removed the player_reference = body without notice? Anyways, if is working now is what matters, just make the question as solved and have a nice coding.

1 Like

Yeah the error didn’t make sense to me either. But it works without issue now so no complaints either!

One more question, if I have multiple objects across the game that requires to be paused during interaction, do I have to implement var player_reference in every scene’s script?

No, you can use get_tree().paused = true to pause the entire game, just make sure to set the node that is dealing with the pause to keep working after the pause, otherwise you’ll lock your entire game.

image
image

I tried to use get_tree().paused = true but because I implemented an animated dialogue box for my NPC, it also pauses the dialogue. I read that changing process mode to Always lets the node run even during pause. But I don’t know how to do this on a script. I read of set_process(PROCESS_MODE_ALWAYS) but not sure where to place it.

In the case of dialogues running while player is paused, which method would be better? get_tree().paused or set_physics_process()?

I also have another issue with logic and conditions. I’ve implemented dialogue box based on a tutorial to improve on how dialogue is displayed. I can’t figure out how to only enable user input for yes/no when the dialogue reaches the end of the array/the last message. Right now, player can change scene anytime during the dialogue. I’ve tried to check for lines.back() as a condition but that didn’t work. And for best practice, would it be better to prevent user input during dialogue, or to check that dialogue is at a point that calls for user input?

Also when I change scene and then come back, the dialogue box and message stays where it last was when player teleported away. If I write something like Dialogmanager.text_box.visible = false, it does go away after changing scenes but I cannot speak to the npc again. Or if I do something like below…

The dialogue doesn’t restart. Dialogue progress only responds to the controls from the dialogmanager script.

I’m wondering if there is a better way to learn the very basics of creating dialogue boxes without turning to dialogic or dialogue manager addon…

func _unhandled_input(event:InputEvent):
	
	if is_player_inside == false:
		return
		
	if Input.is_action_just_pressed("interact"):
		hello.visible = false
		player_reference.set_physics_process(false)
		Dialogmanager.start_dialog(global_position, lines)
		Dialogmanager.text_box.visible = true
		#get_tree().paused = true # pauses dialog as well
		
	if is_player_inside and Input.is_action_just_pressed("yes"):
		get_tree().change_scene_to_file("res://Scenes/town.tscn")
		Dialogmanager.text_box.visible = false
				
	elif is_player_inside and Input.is_action_just_pressed("no"):
		town.visible = false
		notown.visible = true
		#get_tree().paused = false
		player_reference.set_physics_process(true)
		Dialogmanager.text_box.visible = false

full npc script:


var is_player_inside = false

@onready var depart = $depart
@onready var town = $town
@onready var hello = $hello
@onready var timer = $Timer
@onready var notown = $notown

var player_reference

func _ready():
	hello.visible = false
	depart.visible = false
	town.visible = false
	notown.visible = false
	

const lines: Array[String] = [
	"Welcome, stranger!",
	"What brings you to these parts?",
	"Are you looking for the town?",
	"I can show you the way, what do you say? Y/N",
]

func _unhandled_input(event:InputEvent):
	
	if is_player_inside == false:
		return
		
	if Input.is_action_just_pressed("interact"):
		hello.visible = false
		#player_reference.set_physics_process(false)
		Dialogmanager.start_dialog(global_position, lines)
		get_tree().paused = true # pauses dialog as well
		
	if is_player_inside and Input.is_action_just_pressed("yes"):
		get_tree().change_scene_to_file("res://Scenes/town.tscn")
				
	elif is_player_inside and Input.is_action_just_pressed("no"):
		town.visible = false
		notown.visible = true
		get_tree().paused = false
		#player_reference.set_physics_process(true)

func _on_area_2d_body_entered(body):
	if body is CharacterBody2D:
		hello.visible = true
		is_player_inside = true
		player_reference = body
		town.visible = false
		depart.visible = false
		print("player is inside")
		print(body.name)
		print(lines.back())
		

func _on_area_2d_body_exited(body):
	if body is CharacterBody2D:
		is_player_inside = false
		hello.visible = false
		depart.visible = true
		town.visible = false
		notown.visible = false

		timer.start(2.0)
		

func _on_timer_timeout():
	depart.visible = false

Dialogmanager script from DashNothing’s dialogue tutorial

extends Node

@onready var text_box_scene = preload("res://Scenes/textbox.tscn")

var dialog_lines: Array[String] = []
var current_line_index = 0

var text_box
var text_box_position: Vector2

var is_dialog_active = false
var can_advance_line = false

func start_dialog(position: Vector2, lines: Array[String]):
	if is_dialog_active:
		return  #out of this function
		
	dialog_lines = lines
	text_box_position = position
	_show_text_box()
	
	is_dialog_active = true
	
func _show_text_box():
	text_box = text_box_scene.instantiate()
	text_box.finished_displaying.connect(_on_text_box_finished_displaying)
	get_tree().root.add_child(text_box)  # insert textbox into game
	text_box.global_position = text_box_position # assign textbox a position
	text_box.display_text(dialog_lines[current_line_index]) # first time of dialog line
	can_advance_line = false
	
func _on_text_box_finished_displaying():
	can_advance_line = true

func _unhandled_input(event):
	if (
		event.is_action_pressed("advance_dialog")&&
		is_dialog_active &&
		can_advance_line
	):
		text_box.queue_free() # removes text_box
		
		
		current_line_index += 1
		if current_line_index >= dialog_lines.size():
			is_dialog_active = false
			current_line_index = 0
			return
		
		_show_text_box()
		

You’ll use this in the node that have the dialogue control and can also unpause the game

Let’s take this scene tree as example:

image

If you use set_process(PROCESS_MODE_ALWAYS) in Node A, all the nodes and scripts will work even when paused, if you use this in Node B, only nodes B, B2 and B3 will work when paused, if you use in Node C, only C, C2 and C3, and in D, only D will work.


set_physics_process will only stop the _physics_process callback in the called node, so to do a complete pause you need to use get_tree().paused = true.


This is because you code is fragmented, if you have a node that manages the dialogue, all code related to control the dialogue should be there, but instead you have part on the npc script and other in the dialogue script, you need to concentrate every code related to a task in one place, for the dialogue case everything should be inside the dialogue script. Your npc script should only start the dialogue, after that your dialogue script needs to handle pausing the game, the dialogue, unpause the game and what happens from the choices you do.

For the question which node should keep processing when the game is paused the answer is the dialogue script, so you should make every dialogue operation insde them and unpause when everything is done.


Probably because you don’t restart the current_line_count to zero, also if you change the visibility of the node, you need to make him visible again when the dialog starts.


This is not about dialogue systems, is about progamming logic, everything is logic, is know what tools you have (how the classes and their methods works), break the big task in small tasks (instead think of the entire dialogue system, think about the steps, like: “How i make dialogue only starts when player is doing x”, “how i make the next line show”, etc) and execute the small tasks and more important, lots and lots of reading of the docs and praticing doing small tasks. This is for every part of your game, dialogue, inventory, moving, combat, saving progress, etc. This takes time, so you need to study and study to improve your programming logic.

This is because you code is fragmented, if you have a node that manages the dialogue, all code related to control the dialogue should be there, but instead you have part on the npc script and other in the dialogue script, you need to concentrate every code related to a task in one place, for the dialogue case everything should be inside the dialogue script. Your npc script should only start the dialogue, after that your dialogue script needs to handle pausing the game, the dialogue, unpause the game and what happens from the choices you do.

I see! Yeah that makes sense now. I’ll rethink the dialogue system I have in place, maybe redo it. I think I was intimidated to modify the tutorial’s code so I kept hacking away at my own npc script trying to make it work. But I think this method is not constructive for me.

This is not about dialogue systems, is about progamming logic, everything is logic, is know what tools you have (how the classes and their methods works), break the big task in small tasks (instead think of the entire dialogue system, think about the steps, like: “How i make dialogue only starts when player is doing x”, “how i make the next line show”, etc) and execute the small tasks and more important, lots and lots of reading of the docs and praticing doing small tasks. This is for every part of your game, dialogue, inventory, moving, combat, saving progress, etc. This takes time, so you need to study and study to improve your programming logic.

Yeah I don’t think my programming logic is being exercised enough. I think I also struggle with finding a direction when I’m trying to implement something such as what tools can I actually use when I’m still not even familiar with the tools available. Documentation have been helpful but also daunting because sometimes I don’t know what I should be looking for. I can understand logic in simple tutorials and replicate it for practice, but formulating my own is still difficult. I will study more and reduce my scope to steps that can be broken down simply. I’ve learned a lot in this thread, thank you.

About that, keep an eye on that post: How to learn Godot and it's API without tutorials, we’re discussing exactly about that.

1 Like

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