• 2D
  • Using Raycast2D to test if there is collision between two points (isometric tilemap)

Hello everybody! I am new in this forum and to Godot, and I am trying to use raycast2D functionnality to test if one point of my scene is in visual contact with another one in an isometric tilemap. However, the resulting behavior is inpredictible so I guess I did not plainly understand how it works. This is why I am asking you some help :)

Description of my case:

I am using for my isometric grid, a tileset of three tile types: walls, ground, and lighted ground. I have two characters, one animated and moves with the arrows and the other not animated and moves with a,w,s,d. Both movements are cell-by cell, and the program testes if the destination cell is a wall and in that case does not move. For a game I will create later, I need a function that testes, for two input cells, if there is a wall between their centers. An image is always better:

My idea was to put a collision polygon on the wall tile, so that when I create a Raycast2D between the center of my ogre case and the center of my godot icon case, I can know immediately if there is a wall between these.

Here is my architecture:

The function to test if there is visual contact between two cases is in the script of TileMap "Ground":

func areInVisualContact(mapPosition1,mapPosition2): var worldPosition1 = map_to_world(mapPosition1) var worldPosition2 = map_to_world(mapPosition2) var space_state = get_world_2d().direct_space_state var intersections = space_state.intersect_ray(worldPosition1, worldPosition2) print(intersections) if intersections: print(intersections.collider.name) return false else: return true

Both characters have pretty much the same script, and at each move of one of them the program is trying to (but not succeeding) test if both of them are in visual contact, and highlights their cases using third tile type when this is the case.

However, the results are quite random and I get intersections when there is no wall in between... More than that, the function "areInVisualContact" do not have the same behavior for its two variables. intersect_ray do not have a symetric behavior? Any ideas?

Here are the scripts and outputs:

For ogre:

extends KinematicBody2D

onready var player = get_node(".")
onready var playerSprite = get_node("PlayerSprite")
onready var tileMap = get_node("../..")
onready var grid = get_node("..")
onready var walls = get_node("../../Walls")
onready var ground = get_node("../../Ground")
onready var otherCharacter = get_node("../VirtualBody")

export var playerTime = 4.0/25.0
var velocity = 0
var currentTileIndex = -1
var destinationTileIndex = -1

# Called when the node enters the scene tree for the first time.
func _ready():
	cellSize = grid.get_cell_size()
	mapUpVector.x = 0
	mapUpVector.y = -1
	mapRightVector.x = 1
	mapRightVector.y = 0
	
	mapCurrentPosition = grid.world_to_map(playerSprite.position)
	startPosition = grid.map_to_world(mapCurrentPosition)
	currentPosition = get("global_position")
	set("Node2D/transform/position",startPosition)
	
	originString = "Right"
	destinationString = "Right"
	originDirection = rightVector
	destinationDirection = rightVector
	currentAnimation = "IdleRight"
	playerSprite.play(currentAnimation)
	
func get_input():
	currentAnimation = playerSprite.get_animation() #Toujours la apres l'animation?
	if currentAnimation.find("Idle") == 0: #ne prend les inputs que lorsque le personnage est arrete
		if Input.is_action_pressed("ui_down"):
			mapDestinationPosition = mapCurrentPosition-mapUpVector
			destinationTileIndex = walls.get_cellv(mapDestinationPosition)
			destinationString = "Down"
			if destinationTileIndex != 0:
				destinationPosition = grid.map_to_world(mapDestinationPosition)
				destinationDirection = destinationPosition - currentPosition
			return true
		elif Input.is_action_pressed("ui_left"):
			# Idem but left
		elif Input.is_action_pressed("ui_up"):
			# Idem but up
		elif Input.is_action_pressed("ui_right"):
			# Idem but right
	else:
		return false

func _process(delta):
	if get_input():
		if destinationString != originString:
			turningAnimationString = "Turning"+destinationString+"From"+originString
			playerSprite.play(turningAnimationString)
		else:
			if destinationTileIndex != 0:
				velocity = sqrt(destinationDirection.dot(destinationDirection))/playerTime
			else:
				velocity = 0
			walkingAnimationString = "Walking"+destinationString
			playerSprite.play(walkingAnimationString)
	#Move the sprite using direction and velocity
	var motion = destinationDirection.normalized() * (velocity * delta)
	move_and_collide(motion)
	currentPosition = get("global_position")
	if ((currentPosition - destinationPosition).dot(destinationDirection) > 0) && (destinationTileIndex !=0 ):
		set("global_position", destinationPosition)
		ground.set_cellv(mapCurrentPosition,1)
		mapCurrentPosition = mapDestinationPosition
		velocity = 0
		playerSprite.play("Idle"+destinationString)
		otherCharacter.call("refreshLight")
		refreshLight()
		
func refreshLight():
	if ground.areInVisualContact(mapCurrentPosition,otherCharacter.mapCurrentPosition):
		ground.set_cellv(mapCurrentPosition,2)
	else:
		ground.set_cellv(mapCurrentPosition,1)

func _on_PlayerSprite_animation_finished():
	#... Not of interest here

`

For Godot icon:


    onready var otherCharacter = get_node("../KinematicBody2D")
    
    # Called when the node enters the scene tree for the first time.
    func _ready():
    	#...
    	mapCurrentPosition = grid.world_to_map(Vector2(1,0))
    	set("global_position",currentPosition)
    	#...
    	
    func get_input():
    	if Input.is_action_just_pressed("ui_down2"):
    		#...
    	elif Input.is_action_just_pressed("ui_left2"):
    		#...
    	elif Input.is_action_just_pressed("ui_up2"):
    		#...
    	elif Input.is_action_just_pressed("ui_right2"):
    		#...
    	#...

    func _process(delta):
    	i#...
    	if ((currentPosition - destinationPosition).dot(destinationDirection) > 0) && (destinationTileIndex !=0 ):
    		set("global_position", destinationPosition)
    		ground.set_cellv(mapCurrentPosition,1)
    		mapCurrentPosition = mapDestinationPosition
    		velocity = 0
    		otherCharacter.call("refreshLight")
    		refreshLight()
    		
    func refreshLight():
    	#...

Outputs:

{} {} {} {} {collider:[TileMap:1329], collider_id:1329, metadata:(-2, -4), normal:(-0.447214, 0.894427), position:(256, -256), rid:[RID], shape:0} Walls {collider:[TileMap:1329], collider_id:1329, metadata:(-2, -4), normal:(-0.447214, -0.894427), position:(256, -256), rid:[RID], shape:0} Walls

I am sorry it was a bit complicated to explain I hope you have understood... Thank you very much for your help! Don't hesitate with further questions!

Guillaume

PS: I had a limit of characters, so the code you see here is not exact, I replaced some parts of it by comments.

Hum, the most important par did not display well:

func areInVisualContact(mapPosition1,mapPosition2):
	var worldPosition1 = map_to_world(mapPosition1)
	var worldPosition2 = map_to_world(mapPosition2)
	var space_state = get_world_2d().direct_space_state
	var intersections = space_state.intersect_ray(worldPosition1, worldPosition2)
	print(intersections)
	if intersections:
		print(intersections.collider.name)
		return false
	else:
		return true

I have found the origin of my problem, so I answer to my own post so that if someone runs into the same problem he can get an idea of where it comes from.

It was related with this issue: https://github.com/godotengine/godot/issues/2231

What improved my detection of an intermediary collision shape between my two points was playing with my tile offset.

Here is my bloc sprite:

When I added my collision polygon :

The solution that improved (but not working 100% yet) my case:

Why is that so? In order to display my sprite with good alignment with the tilemap (with "top left" alignment), I needed to put an offset of -62 pixels on the tile in y direction. Then, I aligned my collision polygon so that it matches the borders of the wall (at ground level) on the tileset.

And here was my mistake! Even if the collision polygon is a child of the tile sprite, the offset you put on the sprite is actually not used for the collision polygon. The offset used for the collision polygon is automatic, equal to half the size of the sprite. This means that:

  • The image was printed from the top left of the cell, after going up following the -62 pixels y-offset.
  • The collision sprite, on the other hand, had an automatic y-offset of half the height of 190 pixels, i.e. -95 pixels. I needed to correct by putting y position of the shape to -33.

I think this lack of correlation between collision shape offset and sprite offset is counterintuitive and that it would be interesting to fix it :) . Apart from that I have a very nice time using Godot.

Nice evening!