How to make it so that the player that moves via tilemap move forever when an arrow key is pressed?

Godot Version

4.3

Question

i made a character that the player can control and it moves via tiles and tilemap, i wanna make it so that the player can move forever when an arrow key is pressed (and moving to the direction of said arrow key) until it hits a wall (or non-walkable tile)

The code

extends Sprite2D

class_name PacMan

@export var move_speed: float = 1



@onready var tile_map: TileMap = $"../TileMap"

@onready var sprite_2d: Sprite2D = $Sprite2D

@onready var animation_player: AnimationPlayer = $Sprite2D/AnimationPlayer


var is_moving = false

func _physics_process(delta: float) -> void:
	if is_moving == false:
		return
	if global_position == sprite_2d.global_position:
		is_moving = false
		return
	sprite_2d.global_position = sprite_2d.global_position.move_toward(global_position, move_speed)

func _ready() -> void:
	animation_player.play("chomping")

func _process(delta: float) -> void:
	if is_moving:
		return
	if Input.is_action_pressed("up"):
		rotation_degrees = 90
		move(Vector2.UP)
		is_moving = true
	elif Input.is_action_pressed("down"):
		rotation_degrees = 270
		move(Vector2.DOWN)
		is_moving = true
	elif Input.is_action_pressed("left"):
		rotation_degrees = 0
		move(Vector2.LEFT)
		is_moving = true
	elif Input.is_action_pressed("right"):
		rotation_degrees = 180
		move(Vector2.RIGHT)
		is_moving = true

func move(direction: Vector2):
	var current_tile: Vector2i = tile_map.local_to_map(global_position)
	var target_tile: Vector2i = Vector2i(
		current_tile.x + direction.x,
		current_tile.y + direction.y,
	)
	var tile_data: TileData = tile_map.get_cell_tile_data(0, target_tile)
	if tile_data.get_custom_data("walk_tiles") == false:
		return
	#Player movement
	is_moving = true
	global_position = tile_map.map_to_local(target_tile)
	sprite_2d.global_position = tile_map.map_to_local(current_tile)

The is_action_pressed indicates to start doing something, but you can use the is_action_released to indicate to stop doing it. (ie press a button and keep moving left until the button is released.)

For instance:

# Example from my input manager
	if event.is_action_pressed("thrust_forward"):
		# activate thrusting forward
	elif event.is_action_released("thrust_forward"):
		# de-activate thrusting forward

You would have to lose your if is_moving: return line.

func _process(delta: float) -> void:
	if Input.is_action_pressed("up"):
		# do the moving up thing
	elif Input.is_action_released("up"):
		# stop doing the moving up thing
       # etc

alright, but about that MovementManager, whats that exactly? whats the movement manager function

Sorry, it is just another node with all my movement managed in one place. Like thrust forward, turn, etc. (No _process in this file). The different states from the state manager calls these functions to move the ship.

I did not mean to confuse you, just pasting a bit of code from my own game to show using the pressed and released functionality.

My player has children called things like MovementManager, VisualsManager, InputManager, StateManager, LifeManager, OrdinanceManager etc. These all use shared components I can reuse on my other actors, like allies, enemies, objects etc.

PS I have edited out those confusing bits. Sorry again.

its ok
btw whats “event” in the first code you showed? because i typed it in and it gave me “InputEvent”

It is the input process like this:

func _input(event):
	if event.is_action_pressed("turn_left"):
              # Do the turning left thing

_input is only processed when an input is detected. There are lots of these built in notifications:

_ready():

_enter_tree():

_exit_tree():

_process(delta):

_physics_process(delta):

_draw():

The advice is:

While it is possible, to achieve the best performance, one should avoid making input checks during these callbacks. _process() and _physics_process() will trigger at every opportunity (they do not “rest” by default). In contrast, *_input() callbacks will trigger only on frames in which the engine has actually detected the input.

ok
and now what is left to make it so the player goes forever (until it hits a non-walkable tile) when a key is pressed?

Something like this (to still talk to your move function):


var movement_direction: String = ""

func _input(event):
	if event.is_action_pressed("up"):
		movement_direction = "up"
	elif event.is_action_released("up") and movement_direction == "up":
		movement_direction = ""
	
	if event.is_action_pressed("down"):
		movement_direction = "down"
	elif event.is_action_released("down") and movement_direction == "down":
		movement_direction = ""


func _physics_process(delta: float) -> void:
	if movement_direction == "up":
		rotation_degrees = 90
		move(Vector2.UP)
	elif movement_direction == "down":
		rotation_degrees = -90
		move(Vector2.DOWN)

The reason for the additional ‘and’ in the if statements in the input process, is that sometimes you will press left before you release up. If you just set movement_direction to “” on the up button release, it will stop the left movement as well, which I presume you do not want.

I hope that helps to answer your original question about continuous movement on holding a key down.

PS Your move function will now move a tile every frame. You may want to add a timer in there, so after a move to the next tile, a timer starts stopping further movement. When the timer is completed, and the key is still being held, you move to the next tile. This would also mean that on different levels you can speed up your PacMan, if you want to, by reducing the timer delay value. Or speed him up when he gets a power up etc.

i tried that code but the player doesnt move anymore
btw i wont rlly need a timer, ill just use the normal pac-man speed, plus i already have a move_speed variable

Did you leave in the is_moving variable?

	if is_moving:
		return

Post your code again and lets have a look for you.

PS Your move function does not have anything about speed in there. You fetch the appropriate tile (next one up say) and set the global position to be that. I was imagining that it would jump from one tile to the next.

var movement_direction: String = ""


func _input(event):
	if event.is_action_pressed("up"):
		movement_direction = "up"
	elif event.is_action_released("up") and movement_direction == "up":
		movement_direction = ""
	
	if event.is_action_pressed("down"):
		movement_direction = "down"
	elif event.is_action_released("down") and movement_direction == "down":
		movement_direction = ""
	
	if event.is_action_pressed("left"):
		movement_direction = "left"
	elif event.is_action_released("left") and movement_direction == "left":
		movement_direction = ""
	
	if event.is_action_pressed("right"):
		movement_direction = "right"
	elif event.is_action_released("right") and movement_direction == "right":
		movement_direction = ""

There is nothing wrong with that code. I meant to see how it is interacting with all your other code.

I did something similar with my helicopter game demo, but that also has acceleration so the code won’t quite match your requirements.

oh
well idk how it interacts with other code soooo

but i am sure the rest of my script still works
but the player still doesnt move

Post your player script again like you did in the original post and I will take a look for you.

ok

extends Sprite2D

class_name PacMan

@export var move_speed = 1

@onready var player: PacMan = $"."




@onready var tile_map: TileMap = $"../TileMap"

@onready var sprite_2d: Sprite2D = $Sprite2D

@onready var animation_player: AnimationPlayer = $Sprite2D/AnimationPlayer


var is_moving = false

func _physics_process(_delta: float) -> void:
	if is_moving == false:
		return
	if global_position == sprite_2d.global_position:
		is_moving = false
		return
	sprite_2d.global_position = sprite_2d.global_position.move_toward(global_position, move_speed)

func _ready() -> void:
	animation_player.play("chomping")

var movement_direction: String = ""

func _input(event):
	if event.is_action_pressed("up"):
		movement_direction = "up"
	elif event.is_action_released("up") and movement_direction == "up":
		movement_direction = ""
	
	if event.is_action_pressed("down"):
		movement_direction = "down"
	elif event.is_action_released("down") and movement_direction == "down":
		movement_direction = ""
	
	if event.is_action_pressed("left"):
		movement_direction = "left"
	elif event.is_action_released("left") and movement_direction == "left":
		movement_direction = ""
	
	if event.is_action_pressed("right"):
		movement_direction = "right"
	elif event.is_action_released("right") and movement_direction == "right":
		movement_direction = ""



func move(direction: Vector2):
	var current_tile: Vector2i = tile_map.local_to_map(global_position)
	var target_tile: Vector2i = Vector2i(
		current_tile.x + direction.x,
		current_tile.y + direction.y,
	)
	var tile_data: TileData = tile_map.get_cell_tile_data(0, target_tile)
	if tile_data.get_custom_data("walk_tiles") == false:
		return
	elif tile_data.get_custom_data("gate") == true:
		return
	#Player movement
	is_moving = true
	global_position = tile_map.map_to_local(target_tile)
	sprite_2d.global_position = tile_map.map_to_local(current_tile)

I am sorry if the example code I did for you was confusing.

To make a character move on keypress, you normally set a flag such as move_direction or is_moving when the key is pressed, and unset it when the key is released. I hope that bit makes sense to you.

Another process reads the flag and if it is set, it moves the character. I presume that bit is clear too.

In your code, you have two movement things (referring to your original code)

In process:

In your move function:

This is confusing me and without seeing the entire thing, ie, the tree structure and game structure, I really do not know what you are trying to achieve here.

You have a variable called sprite_2d:

Which is a child of whatever node this script is attached to. This script is a class called ‘PacMan’, so I presume this is your actual player node (especially since it has player input in it). However you move both this node, and the sprite_2d node. Both are moved to the next tile, and then you move_toward the sprite 2D to this scripts node. Suerly they are already both the same. (The first quote).

So I started to edit your code, but am now confused about what it is trying to do. So sorry, but I cannot edit this for you because I am worried it is going to cause you more problems in the rest of your code.

Especially now this has appeared:

I hope at least you get how to fire a movement on key-press and stop it on key-release now. How you tie that method into your game code is up to you now. Again, I really wanted to help but cannot given that I have many unanswered questions about your code and game mechanics.

I am sorry, and I hope you find a solution to your particular games code mechanics. At least now you have the method for continuous movement on keypress and I hope you are able to implement that method into your own code.

alright