Can't understand how to get collisions to work for my grid game

Godot Version

v4.6.2

Question

Hi, I’m new to Godot and programming as a whole and I need help. I feel like the answer to this is very obvious but I just can’t figure it out.

My game an animal ecosystem based on a grid - though it’s not technically on a grid but I sort of ‘simulate’ a grid by only allowing the player to move the exact tile size and spawning them in the direct center of the tile. There’s no move_and_slide or physics (at least I don’t think?), because my player character is an Area2D shape and moves a specified amount towards x or y based on input. The player reads a Move/Input script to move. Here’s what it looks like:

The only collisions in the entire game will be with the walls on the outside of the map - which are on the same mask/layer as the player.

The MapBorder is a RigidBody2D and for the collision I used a polygon.

image

My problem is this:

I need a way for my player character to not move the direction the raycast is facing if the raycast is colliding. The raycast is doing a great job - it tells me when it collides with the map boundary but I am at a loss for how to tell it to specifically not move the direction the raycast is colliding in.

Disclaimer: I know Area2D shapes don’t collide - they just recognize other shapes, so an easy solution would be to switch the player to a CharacterBody2D but it didn’t work when I tried (though I might’ve done it wrong) and I want to give the Area2D a shot.

My code for the Movement + Input is below. It’s located inside that MoveInputComponent node in the first image.

My trouble is towards the bottom of the code, under the func change_ray, specifically in the ‘if ray is colliding’ section

class_name MoveInputComponent extends Node
#coded specifically for player movement

@onready var area: Area2D = $".."
@onready var sprite
@onready var ray: RayCast2D = $"../RayCast2D"

var last_direction = Vector2.RIGHT #starting position of raycast
var tile_size = 64
var SPECIES_COOLDOWN: float
#CODE THAT I KNOW WORKS IS BELOW THIS LINE

func _input(event):
	if event.is_action_pressed("left"):
		area.global_position.x -= tile_size # move left on map
		move_delay() #delay movement by species amount
		#sprite.flip_h = true
	if event.is_action_pressed("right"):
		area.global_position.x += tile_size
		move_delay()
		#sprite.flip_h = false
	if event.is_action_pressed("up"):
		area.global_position.y -= tile_size
		move_delay()
	if event.is_action_pressed("down"):
		area.global_position.y += tile_size
		move_delay()
	change_ray() 
	
func change_ray(): #updating ray position + collision
	var input_dir = Input.get_vector("left", "right", "up", "down") #get position facing
	if input_dir != Vector2.ZERO: #if input is not nothing:
		last_direction = input_dir #last_direction is last direction
	ray.target_position = last_direction * tile_size #ray faces last direction
	ray.force_raycast_update() #raycast updates position
	if !ray.is_colliding():
		pass #don't know what to do here

func move_delay(): #creates a delay between when player can press the input again

if not (move_and_collide(motion, true)): (insert code to move)

the above piece of code can be used to check for collisions. First you just declare a variable that stores the direction (i called it motion here), then use the check above.

var motion: Vector2.Zero

if Input.is_action_pressed(“up”):
motion.y = -1
moved_recently()
elif Input.is_action_pressed(“down”):
motion.y = 1
moved_recently()
elif Input.is_action_pressed(“left”):
motion.x = -1
moved_recently()
elif Input.is_action_pressed(“right”):
motion.x = 1
moved_recently()

if motion == Vector2.ZERO: return
if not (move_and_collide(motion, true))
area.global_position += motion * tile_size

i’m new myself so the code might not run without bugs, but the basic idea is to implement the check before the actual movement.

1 Like

try if $Raycast2D.is_colliding():
–whatever to make movement stop

rather than a while not.

1 Like

see my code for movement in my own game;


func _physics_process(_delta: float) -> void:
	#Animator
	animation_controller()
	if Snes.hero_movable == true:
		if sliding_true:
			return
			
		if Input.is_action_pressed("UP"):
			dir = "up"
			if COLLIDE_UP.is_colliding():
				return
			else:
				
				moving = true
				move(Vector2(0, -Snes.tilesize))
				
		elif Input.is_action_pressed("DOWN"):
			dir = "down"
			if COLLIDE_DOWN.is_colliding():
				return
			else:
				moving = true
				move(Vector2(0, Snes.tilesize))
				
		elif Input.is_action_pressed("LEFT"):
			dir = "left"
			if COLLIDE_LEFT.is_colliding():
				return
			else:
				moving = true
				move(Vector2(-Snes.tilesize, 0))
			
		elif Input.is_action_pressed("RIGHT"):
			dir = "right"
			if COLLIDE_RIGHT.is_colliding():

				return
			else:
				moving = true
				move(Vector2(Snes.tilesize, 0))
			
		else:
			moving = false
			body.speed_scale = 1
			arms.speed_scale = 1

func move(offset: Vector2):
	if Snes.hero_movable == true:
		sliding_true = true
		var target_pos = (position + offset).snapped(Vector2(Snes.tilesize, Snes.tilesize))
		var tween = create_tween()
		body.speed_scale = 2
		arms.speed_scale = 2
		tween.tween_property(self, "position", target_pos, speed).set_trans(Tween.TRANS_LINEAR)
		tween.tween_callback(func(): sliding_true = false)

And as for CharacterBody2ds, yes, grid based collision wont work with thems, i still use them for the heck of it,
and yes it is probably innefficient that i am using 4 raycasts, but there is a reason i did that one—not to say you should

#Snes.tilesize is a global integer variable of value 8, just FIY

1 Like

Thank you both you were such a huge help! Seeing yall’s code helped a bunch.

I added a check for seeing if the ray was colliding before every single input and if it was, then return. That did the trick but then if you tapped fast enough, you could go sideways thru the wall - so I moved the function that changed the ray to BEFORE that.

And it’s finally working holy cow. Thank you so much.

I can’t believe I had the check for the ray collision in the wrong spot the whole time haha!

Here’s what it ended up looking like:

if event.is_action_pressed("left"):
		change_ray() 
		if ray.is_colliding():
			return
		else:
			player.global_position.x -= tile_size
1 Like

Do you think it’s worth it to set up an actual grid and snap my character to it or is just moving them by the tile size everywhere fine?

1 Like

Happy to help!
As for grids, I am not sure how you would go about setting a LITERAL grid,
I just make the grid-based movement and I paired it by making my game a low resolution, but that is also because it is designed with Super NES in mind,
If your game is a pixel game that has no modern special effects, then go for it, but i would recommend calculating the ratio based off of the current screensize in that situation.

If you want higher resolution game, then just be careful about moving the player :sweat_smile:

Note, moving the character out of the grid will offput the collisions and everything, not a big deal generally, but it is something to consider.

1 Like

Okay, thank you!

1 Like

This has nothing to do with the actual question, but you might want to look up TileMapLayer node eventually. It will let you build your entire world easily and fits the grid thing perfectly. It’s a bit tough at first, but quite powerful if you stick with it.

Another thing you can do right now is to set up a sort of global class to hold all your game constants. The tile size variable or constant is going to get really annoying for you to keep constant throughout your project. This also has the advantage of letting you change your tile size for everything in the game in one place. If you don’t already know it, you set this up by right clicking the file dock, then add new script, then at the top you write class_name GameConstants (or whatever name you like)

class_name GameConstants

const TILE_SIZE: int = 64

When you want it in any script, you just go GameConstants.TILE_SIZE. It’s very convenient.

Yes! I do use TileMapLayers because I saw TileMap was deprecated, and I found I liked how I could layer the environment with them. Right now all my art/tilesets are from itch.io but I would like to eventually get some sort of grid going later - even if it’s just a mostly transparent TileMapLayer grid. The game I’m creating is inspired by a Java grid game from the early 2000s - so it’s not necessary for me to have a grid but something I would like.

As for the constants - thank you that is a great idea actually and I will be implementing it as soon as possible.

1 Like