Hello, this is my first time posting on this forum, but I hope that my issue can be easily resolved.

I'm currently developing a Monster Breeder style game, but I'm new to Godot and game development in general, so I apologize if I made some glaring mistake.

Here I have a Scene with a Node2D as root, a TileMap and a Sprite Node for the player.

The Player.gd pastebin: https://pastebin.com/x9dgW62a.

My issue is that collisions work if you press the arrow keys without holding them down for too long, but if you hold them down, the player goes right through walls, and can get trapped inside them if they end their movement in one. Also, movement is choppy with a stutter that appears after some seconds of gameplay if you keep holding down the movement buttons.

Thank you for reading, and best regards.

I have never played Monster Breeder, and a quick Google search didn't yield anything that would help me figure out how movement is supposed to work, so I'm kinda guessing how the player is intended to move :sweat_smile:

The issue is likely because you're movement is not stopping when the raycast collides with something. To fix it, you just need to stop the tween once the player is moving. Changing _process to something like this should work (untested):

func _process(delta):
	 _get_direction()
	
	if (ray.is_colliding() == true):
		target_position = position
		tween.stop_all()
	elif(!ray.is_colliding() && position == target_position):
		target_position = position + direction * tile_size
	
	_move_player()

That way when the raycast node detects a collision, it should stop the player from moving, which should make it where the player cannot move through walls.

Also, if you want to set TILE_SIZE in game, you just need to do the calculation in the _ready function or add onready before var when you are defining the variable.

Hopefully this helps a bit :smile: (Side note: Welcome to the forums)

Thank you for your reply! When I wrote "Monster Breeder style", I didn't realize that there was a game with that name. What I mean is a game like Digimon or Pokemon, which have you walk in a grid.

Unfortunately, although we're getting somewhere, now the player moves a bit too much inside the wall, which messes up with the 64x64 grid of the game.

Like so:

Also, thanks to your advice, I could now use the TILE_SIZE constant from the Game Node!

After taking a little break I found a (somewhat) solution to the problem above. I created a variable called last_position and I just use it to reset the player back to their last position once a collision occurs. This, however, created a different problem, because now if the player is moving alongside a wall and tries to walk into it, they'll visibly move halfway through the target tile, then zap back to their original tile.

https://pastebin.com/uevEyscr

Thank you for any help!

@GreyGoldFish said: Thank you for your reply! When I wrote "Monster Breeder style", I didn't realize that there was a game with that name. What I mean is a game like Digimon or Pokemon, which have you walk in a grid.

Oh!

Unfortunately, although we're getting somewhere, now the player moves a bit too much inside the wall, which messes up with the 64x64 grid of the game.

Like so:

Hmm, maybe the size of the Raycast needs to be increased slightly? Perhaps the end of the Raycast is stopping just before it can reach a tile, so it doesn't detect it until the player has moved into the grid.

I do not know if you have seen it or not, but a user called fornclake on Youtube has made a Grid-based movement tutorial that uses a raycast and a tilemap, which looks like it might be similar to how you are detecting tiles.

After taking a little break I found a (somewhat) solution to the problem above. I created a variable called "last_position" and I just use it to reset the player back to their last position once a collision occurs. This, however, created a different problem, because now if the player is moving alongside a wall and tries to walk into it, they'll visibly move halfway through the target tile, then zap back to their original tile.

Hmm, so maybe the collision detection is delayed or something.

I don't know if this will help or not, but maybe add ray.force_raycast_update() before checking for collisions in _process and see if that helps? Another thing that might be worth trying is changing _process to _physics_process so the code is in sync with the physics world.

Thank you for your continued suggestions!

I actually based this grid movement on Fornclake's design, but with a Tween instead!

When I either added ray.force_raycast_update() or changed _process to _physics_process this was the result:

I think that the issue might be that the ray cast is triggering the _move_player function when it shouldn't, so I was looking into changing the code of _get_direction which casts the ray cast, but I had no success.

This is what is happening, for added clarity:

All I'm doing is pressing up, then left afterwards.

Cheers!

EDIT: Current code, for reference: https://pastebin.com/b949UQG9

I downloaded the finished project from Fronclake's video since I didn't have a ready test project. I replaced the code and after some testing was able to find the issues, or at least something similar to them.

I changed the code a bit so the raycast only checks when the player is not in movement. This makes the assumption that the player will not be moving when the player does not press any input, but formatting the code this way should make it where the player does not teleport back to the last position when moving nearby a wall.

Here is the modified code:

extends Sprite
 
var last_position = Vector2()
var target_position = Vector2()
var direction = Vector2()
 
export(float) var speed = 5
 
# References const TILE_SIZE from the Game Node2D
#onready var TILE_SIZE = get_parent().get_parent().TILE_SIZE
# Had to change slightly due to scene differences
onready var TILE_SIZE = get_parent().get_node("TileMap").cell_size.x
 
onready var ray = $RayCast2D
onready var tween = $Tween

var is_player_moving = false
 
func _ready():
	speed = 1 / speed
	
	position = position.snapped(Vector2(TILE_SIZE / 2, TILE_SIZE / 2))
	
	target_position = position
	
	tween.connect("tween_completed", self, "_on_tween_completed");
 
func _process(delta):
	
	_get_direction()
	
	# If the player is trying to move...
	if (direction != Vector2.ZERO):
		
		# Only attempt to move if the player is currently not moving
		if (is_player_moving == false):
			
			# Get the most up to date raycast collision information.
			ray.force_raycast_update();
			
			# COLLISION == MOVE PLAYER BACK TO LAST POSITION
			if(ray.is_colliding()):
				target_position = last_position
				_move_player()
			
			# NO COLLISION AND PLAYER IS NOT MOVING == MOVE PLAYER TO WHERE THEY WANT
			elif(target_position == position):
				target_position = position + direction * TILE_SIZE
				
				_move_player()
 
func _move_player():
	if (is_player_moving == false):
		tween.interpolate_property(self, "position", position, target_position, speed, Tween.TRANS_LINEAR, Tween.EASE_IN)
		tween.start()
		
		is_player_moving = true
 
func _get_direction():
	var right = Input.is_action_pressed("ui_right")
	var left = Input.is_action_pressed("ui_left")
	var up = Input.is_action_pressed("ui_up")
	var down = Input.is_action_pressed("ui_down")
	
	direction.x = int(right) - int(left)
	direction.y = int(down) - int(up)
	
	# PLAYER TRIES TO MOVE DIAGONALLY
	if(direction.x != 0 && direction.y != 0):
		direction = Vector2.ZERO
	   
	# PLAYER TRIES TO MOVE AT ALL
	if(direction != Vector2.ZERO):
		ray.cast_to = direction * TILE_SIZE / 2


func _on_tween_completed(object, nodepath_key):
	if (nodepath_key == ":position"):
		# The player has stopped moving
		is_player_moving = false
		# Save this position
		last_position = position

I also uploaded the project to my Google Drive.

I was not able to replicate all of the issues, so it might still need adjusting, but hopefully it is of some help!

Thank you so much, you've been a great help to me! Without your help, I don't think that I'd have gotten this far! I implemented all of your suggestions into my own code and it works without a hitch, but there are a few pieces of code that I don't comprehend.

For example, I don't recognize the tween.connect("tween_completed", self, "_on_tween_completed") method.

Also, on the _on_tween_completed function I don't understand what the nodepath_key == ":position" comparation does.

Again, thank you so much for your patience and understanding, as well as the solution for my issue!

No problem! I'm glad I was able to help :smile:

I don't recognize the tween.connect("tween_completed", self, "_on_tween_completed") method.

This connects the tween_completed signal to a function defined in the script (hence self) called _on_tween_completed. I added this because I couldn't find a good way to detect if the Tween node was moving the player or not without connecting the signal.

The documentation has a page about Signals that might be worth looking at, though I have not looked through it myself so I cannot say how useful it will be.


A brief (and probably terrible) explanation of how signals work:

Signals are emitted by nodes when something happens. You can connect a signal to a function so that when the signal is emitted, the connected function is called. You can use this to basically set up the code so that "when X happens, call function Y", which allows you to organize your code. Some things can only be determined by the signals the node emits, which can make things tricky.

Some signals pass variables containing information related to the action that caused the signal to be emitted. In the case of the code, the tween_completed signal is emitted when the Tween node is no longer changing a value, and it passes a reference to the object that was having its value changed, along with a NodePath containing the name of the changed value.

(Let me know if this does not make sense and I'll try to reword it and/or find better references.)

on the _on_tween_completed function I don't understand what the nodepath_key == ":position" comparation does.

What this comparison is doing is checking to see if the value the Tween node just finished changing is the position of the player. This isn't necessarily needed if you only use the Tween node for moving the player, but I wanted to add a safety check of sorts by only setting is_player_moving to false when the position is no longer being changed, just in case you need to use the Tween node later for something else.

To be honest, I'm not 100% sure why the nodepath key is equal to :position instead of position. I'm guessing that if the node was not the player, it would be something like node_name:position, so maybe that is why, but I do not know for sure. I just printed nodepath_key and made the comparison based on the printed value to be on the safe side :smile:

3 years later