Help with turning collisions for different TileMapLayers on and off when a depth value changes

Godot Version

v4.7

Question

Hey guys, total beginner here hoping for some help.

I’m making a little 2.5d Scuba diving game. I’ve got different levels of the seafloor that I’m letting my PC explore by swimming around on a 2D plane, and then pressing a button to dive deeper or raise up a little to give themselves access to deeper trenches, etc.

The player has different animations for walking, wading at different levels, and swimming, so there’s a bit of logic about collision shapes changing etc. There’s also a shadow underneath the player at the lowest depth available so that the player has an easier time keeping track of what tile they’re on when they’re floating three or four levels up. So there’s logic for that too.

What I cannot figure out is how to get the collisions working on each tile map layer as the player dives down. I need the player to only collide with the rocks and tiles when he’s on the same layer as them, otherwise just swim over them. Can someone let me know if there’s somewhere obvious I’m going wrong in this code?

I have this Global script to keep track of a few things:

extends Node

signal depth_changed(new_depth: float)

func get_depth_at_position(target_global_pos: Vector2) -> int:
	var depth_layers: Array[TileMapLayer] = [
			get_node_or_null("/root/World/Seafloor/Seafloor1"),
			get_node_or_null("/root/World/Seafloor/Seafloor2"),
			get_node_or_null("/root/World/Seafloor/Seafloor3"),
			get_node_or_null("/root/World/Seafloor/Seafloor4"),
			get_node_or_null("/root/World/Seafloor/Seafloor5"),
			get_node_or_null("/root/World/Seafloor/Seafloor6"),
			get_node_or_null("/root/World/Seafloor/Seafloor7")
			]
	for i in range(depth_layers.size()):
		var layer: TileMapLayer = depth_layers[i]
		if not layer:
			continue
		var map_pos: Vector2i = layer.local_to_map(layer.to_local(target_global_pos))
		if layer.get_cell_source_id(map_pos) != -1:
			return i
	return 999999
		

var player_depth := 0:
	set(value):
		player_depth = value
		depth_changed.emit(value) 
		
	

The surface world tiles go invisible when he dives:

extends Node2D

func _ready() -> void:
	Global.depth_changed.connect(_on_player_depth_changed)
	_on_player_depth_changed(Global.player_depth)
	
func _on_player_depth_changed(current_depth: float) -> void:
	if current_depth > 0:
		$SurfaceTiles.visible = false
	else:
		$SurfaceTiles.visible = true
		

And I have this seafloor script that shifts the layers z_index so that they draw in the right order around the PC as he dives, so he can be occluded by different layers at different depths. There’s Some ColourRects between each TileMapLayer too with a transparent blue to give a bit of depth:

extends Node2D

func _ready() -> void:
	Global.depth_changed.connect(_on_player_depth_changed)
	_on_player_depth_changed(Global.player_depth)
	
func _on_player_depth_changed(current_depth: float) -> void:
	if current_depth == 1:
		$Seafloor1.z_index = 0
		$Seafloor2.z_index = -20
		$Seafloor3.z_index = -40
		$Seafloor4.z_index = -60
		$Seafloor5.z_index = -80
		$Seafloor6.z_index = -100
		$Seafloor7.z_index = -120
		$ColorRect1.visible = false
		$ColorRect2.visible = true
		$ColorRect3.visible = true
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 2:
		$Seafloor1.z_index = 20
		$Seafloor2.z_index = 0
		$Seafloor3.z_index = -20
		$Seafloor4.z_index = -40
		$Seafloor5.z_index = -60
		$Seafloor6.z_index = -80
		$Seafloor7.z_index = -100
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = true
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 3:
		$Seafloor1.z_index = 40
		$Seafloor2.z_index = 20
		$Seafloor3.z_index = 0
		$Seafloor4.z_index = -20
		$Seafloor5.z_index = -40
		$Seafloor6.z_index = -60
		$Seafloor7.z_index = -80
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 4:
		$Seafloor1.z_index = 60
		$Seafloor2.z_index = 40
		$Seafloor3.z_index = 20
		$Seafloor4.z_index = 0
		$Seafloor5.z_index = -20
		$Seafloor6.z_index = -40
		$Seafloor7.z_index = -60
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = false
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 5:
		$Seafloor1.z_index = 80
		$Seafloor2.z_index = 60
		$Seafloor3.z_index = 40
		$Seafloor4.z_index = 20
		$Seafloor5.z_index = 0
		$Seafloor6.z_index = -20
		$Seafloor7.z_index = -40
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = false
		$ColorRect5.visible = false
		$ColorRect6.visible = true
	elif current_depth == 6:
		$Seafloor1.z_index = 100
		$Seafloor2.z_index = 80
		$Seafloor3.z_index = 60
		$Seafloor4.z_index = 40
		$Seafloor5.z_index = 20
		$Seafloor6.z_index = 0
		$Seafloor7.z_index = -10
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = false
		$ColorRect5.visible = false
		$ColorRect6.visible = false
	else: 
		$Seafloor1.z_index = -20
		$Seafloor2.z_index = -40
		$Seafloor3.z_index = -60
		$Seafloor4.z_index = -80
		$Seafloor5.z_index = -100
		$Seafloor6.z_index = -120
		$Seafloor7.z_index = -140
		$ColorRect1.visible = true
		$ColorRect2.visible = true
		$ColorRect3.visible = true
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true

And finally, I’ve got my PC code:

extends CharacterBody2D

@export var tile_map_layer: TileMapLayer

@onready var UprightColliders = $UprightCollisions
@onready var SwimColliders = $SwimColliders
@onready var Swim_N = $SwimColliders/Swim_N
@onready var Swim_NE = $SwimColliders/Swim_NE
@onready var Swim_E = $SwimColliders/Swim_E
@onready var Swim_SE = $SwimColliders/Swim_SE
@onready var Swim_S = $SwimColliders/Swim_S
@onready var Visuals = $Visuals
@onready var Canvas = $Visuals/CustomisedCanvasGroup


var high_speed := 100.0
var mid_speed := 80.0
var low_speed := 60.0
var player_speed = mid_speed
var is_playing_one_shot_anim := false
var last_tile_coords: Vector2i = Vector2i(-999, -999)
var last_terrain_id: int = -999
var current_depth = 0
var surface = "Land_"
var last_facing_direction: Vector2 = Vector2.DOWN

func _ready() -> void:
	
	Global.depth_changed.connect(_on_player_depth_changed)
	_on_player_depth_changed(Global.player_depth)

func _physics_process(_delta: float) -> void:
	
	var shadow_depth: int = Global.get_depth_at_position(global_position)
	
	check_current_terrain()
	var input_vector := Input.get_vector("move_left", "move_right", "move_up", "move_down")
	
	if input_vector != Vector2.ZERO:
		var normalised_input = input_vector.normalized()
		var iso_vector = Vector2(normalised_input.x, normalised_input.y * 0.5)
		velocity = iso_vector * player_speed
		last_facing_direction = iso_vector.normalized()
	else:
		velocity = velocity.move_toward(Vector2.ZERO, player_speed)
		
	move_and_slide()
	animate_pc()
	_set_shadow(shadow_depth)
		
		
	if Input.is_action_just_pressed("Dive"):
		if surface == "Water_" and Global.player_depth < 6:
			is_playing_one_shot_anim = true
			Global.player_depth += 1
			$AnimationPlayer.play("Dive")
			var tween := create_tween().set_parallel()
			tween.tween_property(Canvas, "position:y", Canvas.position.y + 32, 0.6)
			tween.tween_property(UprightColliders, "position:y", Canvas.position.y + 32, 0.6)
			tween.tween_property(SwimColliders, "position:y", Canvas.position.y + 32, 0.6)
			
	
	if Input.is_action_just_pressed("Surface"):
		if surface == "Water_" and Global.player_depth > 0:
			is_playing_one_shot_anim = true
			Global.player_depth -= 1
			$AnimationPlayer.play("Surface")
			var tween := create_tween().set_parallel()
			tween.tween_property(Canvas, "position:y", Canvas.position.y - 32, 0.6)
			tween.tween_property(UprightColliders, "position:y", Canvas.position.y - 32, 0.6)
			tween.tween_property(SwimColliders, "position:y", Canvas.position.y - 32, 0.6)
			
func check_current_terrain() -> void:
	if not tile_map_layer:
		return
	var map_coord = tile_map_layer.local_to_map(global_position)
	if map_coord == last_tile_coords:
		return
	var tile_data = tile_map_layer.get_cell_tile_data(map_coord)
	var previous_data = tile_map_layer.get_cell_tile_data(last_tile_coords)
	if tile_data == previous_data:
		return
	last_tile_coords = map_coord
	if not tile_data:
		set_movement_state("LAND")
		return
	var current_terrain_id = tile_data.get_terrain()
	match current_terrain_id:
		0: 
			set_movement_state("LAND")
		1:
			set_movement_state("SHALLOW")
		2: 
			set_movement_state("MEDIUM")
		3:
			set_movement_state("DEEP")
		4:
			set_movement_state("WATER")

func set_movement_state(state: String) -> void:
	match state:
		"LAND":
			player_speed = mid_speed
			surface = "Land_"
			go_land()
		"SHALLOW":
			player_speed = low_speed
			surface = "Land_"
			go_shallow()
		"MEDIUM":
			player_speed = low_speed
			surface = "Wade_"
			go_medium()
		"DEEP":
			player_speed = low_speed
			surface = "Wade_"
			go_deep()
		"WATER":
			player_speed = mid_speed
			surface = "Water_"
			go_water()

func animate_pc() -> void:
	if is_playing_one_shot_anim:
		return
	else:
		var is_moving = velocity.length() > 1.0
		var state = "Move" if is_moving else "Idle"
		var angle = rad_to_deg(last_facing_direction.angle())
		var target_anim = ""
		Visuals.scale.x = 1.0
		SwimColliders.scale.x = 1.0
		
		if not is_moving:
			_disable_all_swim_shapes()
			
		if surface == "Water_" and is_moving:
			UprightColliders.disabled = true
		else:
			UprightColliders.disabled = false
			
		if angle > -14.0 and angle <= 14.0:
			target_anim = "E_" + surface + state
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_E.disabled = false
		elif angle > 14.0 and angle <= 76.0:
			target_anim = "SE_" + surface + state
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_SE.disabled = false
		elif angle > 76.0 and angle <= 104.0:
			target_anim = "S_" + surface + state
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_S.disabled = false
		elif angle > 104.0 and angle <= 166.0:
			Visuals.scale.x = -1.0
			SwimColliders.scale.x = -1.0
			target_anim = "SE_" + surface + state # Mirrors SE to make Southwest
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_SE.disabled = false
		elif angle > 166.0 or angle <= -166.0:
			Visuals.scale.x = -1.0
			SwimColliders.scale.x = -1.0
			target_anim = "E_" + surface + state  # Mirrors E to make West
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_E.disabled = false
		elif angle > -166.0 and angle <= -104.0:
			Visuals.scale.x = -1.0
			SwimColliders.scale.x = -1.0
			target_anim = "NE_" + surface + state # Mirrors NE to make Northwest
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_NE.disabled = false
		elif angle > -104.0 and angle <= -76.0:
			target_anim = "N_" + surface + state
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_N.disabled = false
		elif angle > -76.0 and angle <= -14.0:
			target_anim = "NE_" + surface + state
			if surface == "Water_" and is_moving:
				_disable_all_swim_shapes()
				Swim_NE.disabled = false
			
		if $AnimationPlayer.current_animation != target_anim:
			if $AnimationPlayer.has_animation(target_anim):
				$AnimationPlayer.play(target_anim)
			
func go_land() -> void:
	var tween := create_tween().set_parallel()
	tween.tween_property(Visuals, "position:y", 0, 0.8)
	tween.tween_property(UprightColliders, "position:y", 0, 0.8)
	
func go_shallow() -> void:
	var tween := create_tween().set_parallel()
	tween.tween_property(Visuals, "position:y", 5, 0.8)
	tween.tween_property(UprightColliders, "position:y", 5, 0.8)
	
func go_medium() -> void:
	var tween := create_tween().set_parallel()
	tween.tween_property(Visuals, "position:y", 10, 0.8)
	tween.tween_property(UprightColliders, "position:y", 10, 0.8)
	
func go_deep() -> void:
	Global.player_depth = 0
	var tween := create_tween().set_parallel()
	tween.tween_property(Visuals, "position:y", 20, 0.8)
	tween.tween_property(UprightColliders, "position:y", 20, 0.8)
	
func go_water() -> void:
	var tween := create_tween().set_parallel()
	tween.tween_property(Visuals, "position:y", 30, 0.8)
	tween.tween_property(UprightColliders, "position:y", 30, 0.8)


func _on_animation_player_animation_finished(anim_name: StringName) -> void:
	if anim_name == "Dive" or "Surface":
		is_playing_one_shot_anim = false
	else:
		return
		
func _disable_all_swim_shapes():
	for child in SwimColliders.get_children():
		if child is CollisionShape2D:
			child.disabled = true

func _on_player_depth_changed(new_depth: int) -> void:
	if new_depth > current_depth:
		set_collision_mask_value(new_depth, true)
		current_depth = new_depth
	elif new_depth < current_depth:
		set_collision_mask_value(current_depth, false)
	current_depth = new_depth
	
func _set_shadow(depth) -> void:
	if depth == 0:
		$Visuals/Shadow.position = Vector2(0, 0)
	else:
		$Visuals/Shadow.position.y = depth*32
	$Visuals/Shadow.z_index = (depth - Global.player_depth) * -20


Here are pictures of the Node trees:

Massive dump of information, but I’ve been trying a lot of different things and I’m feeling swamped. Massive thanks in advance anyone who is taking the time to look through this, happy to clarify any points if I’ve left out important information.

thank you!

Does it not work to just disable collision for the TileMapLayer? TileMapLayer — Godot Engine (stable) documentation in English

If I disable collisions for the TileMapLayer then my shadow won’t be able to find it to display at the correct depth I think?

edit: alright I’m thinking that because the shadow is checking whether a tile is present or not on each layer rather than checking for collisions it should be a way forward, so I’ve added this code to my seafloor scene:

extends Node2D

func _ready() -> void:
	Global.depth_changed.connect(_on_player_depth_changed)
	_on_player_depth_changed(Global.player_depth)
	
	$Seafloor1.collision_enabled = false
	$Seafloor2.collision_enabled = false
	$Seafloor3.collision_enabled = false
	$Seafloor4.collision_enabled = false
	$Seafloor5.collision_enabled = false
	$Seafloor6.collision_enabled = false
	$Seafloor7.collision_enabled = false
	
func _on_player_depth_changed(current_depth: float) -> void:
	if current_depth == 1:
		$Seafloor1.collision_enabled = true
		$Seafloor2.collision_enabled = false
		$Seafloor3.collision_enabled = false
		$Seafloor4.collision_enabled = false
		$Seafloor5.collision_enabled = false
		$Seafloor6.collision_enabled = false
		$Seafloor1.z_index = 0
		$Seafloor2.z_index = -20
		$Seafloor3.z_index = -40
		$Seafloor4.z_index = -60
		$Seafloor5.z_index = -80
		$Seafloor6.z_index = -100
		$Seafloor7.z_index = -120
		$ColorRect1.visible = false
		$ColorRect2.visible = true
		$ColorRect3.visible = true
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 2:
		$Seafloor1.collision_enabled = false
		$Seafloor2.collision_enabled = true
		$Seafloor3.collision_enabled = false
		$Seafloor4.collision_enabled = false
		$Seafloor5.collision_enabled = false
		$Seafloor6.collision_enabled = false
		$Seafloor1.z_index = 20
		$Seafloor2.z_index = 0
		$Seafloor3.z_index = -20
		$Seafloor4.z_index = -40
		$Seafloor5.z_index = -60
		$Seafloor6.z_index = -80
		$Seafloor7.z_index = -100
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = true
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 3:
		$Seafloor1.collision_enabled = false
		$Seafloor2.collision_enabled = false
		$Seafloor3.collision_enabled = true
		$Seafloor4.collision_enabled = false
		$Seafloor5.collision_enabled = false
		$Seafloor6.collision_enabled = false
		$Seafloor1.z_index = 40
		$Seafloor2.z_index = 20
		$Seafloor3.z_index = 0
		$Seafloor4.z_index = -20
		$Seafloor5.z_index = -40
		$Seafloor6.z_index = -60
		$Seafloor7.z_index = -80
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 4:
		$Seafloor1.collision_enabled = false
		$Seafloor2.collision_enabled = false
		$Seafloor3.collision_enabled = false
		$Seafloor4.collision_enabled = true
		$Seafloor5.collision_enabled = false
		$Seafloor6.collision_enabled = false
		$Seafloor1.z_index = 60
		$Seafloor2.z_index = 40
		$Seafloor3.z_index = 20
		$Seafloor4.z_index = 0
		$Seafloor5.z_index = -20
		$Seafloor6.z_index = -40
		$Seafloor7.z_index = -60
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = false
		$ColorRect5.visible = true
		$ColorRect6.visible = true
	elif current_depth == 5:
		$Seafloor1.collision_enabled = false
		$Seafloor2.collision_enabled = false
		$Seafloor3.collision_enabled = false
		$Seafloor4.collision_enabled = false
		$Seafloor5.collision_enabled = true
		$Seafloor6.collision_enabled = false
		$Seafloor1.z_index = 80
		$Seafloor2.z_index = 60
		$Seafloor3.z_index = 40
		$Seafloor4.z_index = 20
		$Seafloor5.z_index = 0
		$Seafloor6.z_index = -20
		$Seafloor7.z_index = -40
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = false
		$ColorRect5.visible = false
		$ColorRect6.visible = true
	elif current_depth == 6:
		$Seafloor1.collision_enabled = false
		$Seafloor2.collision_enabled = false
		$Seafloor3.collision_enabled = false
		$Seafloor4.collision_enabled = false
		$Seafloor5.collision_enabled = false
		$Seafloor6.collision_enabled = true
		$Seafloor1.z_index = 100
		$Seafloor2.z_index = 80
		$Seafloor3.z_index = 60
		$Seafloor4.z_index = 40
		$Seafloor5.z_index = 20
		$Seafloor6.z_index = 0
		$Seafloor7.z_index = -10
		$ColorRect1.visible = false
		$ColorRect2.visible = false
		$ColorRect3.visible = false
		$ColorRect4.visible = false
		$ColorRect5.visible = false
		$ColorRect6.visible = false
	else: 
		$Seafloor1.collision_enabled = false
		$Seafloor2.collision_enabled = false
		$Seafloor3.collision_enabled = false
		$Seafloor4.collision_enabled = false
		$Seafloor5.collision_enabled = false
		$Seafloor6.collision_enabled = false
		$Seafloor1.z_index = -20
		$Seafloor2.z_index = -40
		$Seafloor3.z_index = -60
		$Seafloor4.z_index = -80
		$Seafloor5.z_index = -100
		$Seafloor6.z_index = -120
		$Seafloor7.z_index = -140
		$ColorRect1.visible = true
		$ColorRect2.visible = true
		$ColorRect3.visible = true
		$ColorRect4.visible = true
		$ColorRect5.visible = true
		$ColorRect6.visible = true

So this should be turning collisions on and off so that they’re only active for the layer that the player is currently swimming in. The other things, the z_index and the ColourRects toggling on and off are working properly, so I think it’s mostly working. But no matter what depth I’m swimming at, I’m not colliding with anything, just swimming right between the layers of rock without impediment. Is there anything obvious that I’m doing wrong?