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!



