Godot Version
4.5.1
Question
Hi all, I am an absolute first timer, but out of the blue was motivated to make a NES-esque skateboarding game. I have graphic and music experience so figured I could easily do the fun bits. I have been using chatGPT to help with script (I apologise if that’s frowned upon - I am wrapping my head around it the more I fiddle though). I’ve been at it for about 6 days. It’s been fairly successful until I wanted to add slopes.
The slope is made through a collision layer on a tile.
I wanted to have sprites change for forwards uphill and backwards downhill. Got it to work for uphill, but down hill has been a multi day battle now and I would truly appreciate any assistance or advice anybody can provide. It’s kind of the last physical action required, then I can actually make the game.
Attached is the full code and I’ll link a little video so you can see it in action. This is the closest I’ve gotten. You can see that if you stop travelling to the left while on the slope it holds the right sprite, but if moving, it flashes between the right sprite and one of the sprite for travelling left on flat ground. In other iterations it would flash between the correct sprite and the falling from a jump sprite - suggesting disconnection from the slope. I’m so confused.
If you share code, please wrap it inside three backticks or replace the code in the next block:
extends CharacterBody2D
# ---------------- MOVEMENT ----------------
@export var speed := 120
@export var gravity := 600
@export var jump_force := -300
@export var floor_friction := 0.85
@export var air_friction := 0.99
# ---------------- SPRITES ----------------
@export var roll_forward_1: Sprite2D
@export var roll_forward_2: Sprite2D
@export var roll_fakie_1: Sprite2D
@export var roll_fakie_2: Sprite2D
@export var uphill: Sprite2D
@export var downhill_fakie: Sprite2D
@export var Ollie_1: Sprite2D
@export var Ollie_2: Sprite2D
@export var sprite_ollie_fakie: Sprite2D
@export var sprite_ollie_fakie_fall: Sprite2D
@export var kickflip_1: Sprite2D
@export var kickflip_2: Sprite2D
@export var kickflip_3: Sprite2D
@export var bs360_1: Sprite2D
@export var bs360_2: Sprite2D
@export var bs360_3: Sprite2D
# ---------------- GRIND SPARKS ----------------
@export var RailTilemap: TileMap
@export var grind_spark_1: Sprite2D
@export var grind_spark_2: Sprite2D
var grind_sparks: Array[Sprite2D] = []
var spark_index := 0
var spark_timer := 0.0
var spark_interval := 0.1
# ---------------- COLLISION ----------------
@export var collision_shape: CollisionShape2D
# ---------------- ANIMATION ----------------
var animation_timer := 0.0
var animation_interval := 0.2
var forward_toggle := true
var fakie_toggle := true
# ---------------- DIRECTION ----------------
var facing_right := true
# ---------------- KICKFLIP ----------------
var kickflip_active := false
var kickflip_timer := 0.0
var kickflip_frame := 0
var last_jump_time := 1.0
var double_jump_window := 0.35
# ---------------- BS 360 ----------------
var bs360_active := false
var bs360_timer := 0.0
var bs360_frame := 0
var bs360_queued := false
# ---------------- DOWNHILL CONTEXT ----------------
var downhill_context := false
func _ready():
grind_sparks = [grind_spark_1, grind_spark_2]
for s in grind_sparks:
if s:
s.visible = false
if uphill:
uphill.position.y += 6
if downhill_fakie:
downhill_fakie.position.y += 6
if roll_forward_1:
roll_forward_1.visible = true
func _physics_process(delta):
animation_timer += delta
last_jump_time += delta
var direction := 0
if Input.is_action_pressed("ui_left"):
direction -= 1
if Input.is_action_pressed("ui_right"):
direction += 1
if direction != 0:
facing_right = direction > 0
var on_rail := _is_on_rail_floor()
if direction != 0:
velocity.x = direction * speed
else:
if is_on_floor():
if on_rail:
velocity.x = speed if facing_right else -speed
else:
velocity.x *= floor_friction
else:
velocity.x *= air_friction
if not is_on_floor():
velocity.y += gravity * delta
else:
velocity.y = 0
if Input.is_action_just_pressed("ui_up"):
if last_jump_time <= double_jump_window and velocity.y <= 0:
kickflip_active = true
kickflip_timer = 0
kickflip_frame = 0
if is_on_floor():
velocity.y = jump_force
elif is_on_floor():
velocity.y = jump_force
last_jump_time = 0
if Input.is_action_just_pressed("ui_down"):
if not is_on_floor() and velocity.y < 0 and not kickflip_active:
bs360_queued = true
if bs360_queued and velocity.y > -40 and velocity.y < 0:
bs360_active = true
bs360_queued = false
bs360_timer = 0
bs360_frame = 0
# ---------------- DOWNHILL CONTEXT ----------------
if is_on_floor() and not kickflip_active and not bs360_active:
var normal: Vector2 = _get_floor_normal()
var slope: bool = abs(normal.x) > 0.05
# downhill true if moving along slope
if slope:
downhill_context = (normal.x > 0 and velocity.x > 0) or (normal.x < 0 and velocity.x < 0)
else:
downhill_context = false
else:
downhill_context = false
move_and_slide()
if kickflip_active:
_handle_kickflip(delta)
elif bs360_active:
_handle_bs360(delta)
else:
_handle_normal_sprites()
_handle_grind_sparks()
# ---------------- FLOOR NORMAL ----------------
func _get_floor_normal() -> Vector2:
for i in range(get_slide_collision_count()):
var collision := get_slide_collision(i)
if collision.get_normal().y < -0.7:
return collision.get_normal()
return Vector2.UP
# ---------------- SPRITES ----------------
func _hide_all_sprites():
for s in [
roll_forward_1, roll_forward_2,
roll_fakie_1, roll_fakie_2,
uphill, downhill_fakie,
Ollie_1, Ollie_2,
sprite_ollie_fakie, sprite_ollie_fakie_fall,
kickflip_1, kickflip_2, kickflip_3,
bs360_1, bs360_2, bs360_3
]:
if s:
s.visible = false
func _show_forward_roll():
if animation_timer >= animation_interval:
animation_timer = 0
forward_toggle = !forward_toggle
if forward_toggle:
roll_forward_1.visible = true
else:
roll_forward_2.visible = true
func _show_fakie_roll():
# only toggle fakie if NOT downhill
if downhill_context:
return
if animation_timer >= animation_interval:
animation_timer = 0
fakie_toggle = !fakie_toggle
if fakie_toggle:
roll_fakie_1.visible = true
else:
roll_fakie_2.visible = true
func _handle_normal_sprites():
_hide_all_sprites()
if is_on_floor():
var normal: Vector2 = _get_floor_normal()
var slope: bool = abs(normal.x) > 0.05
var uphill_here: bool = slope and normal.x < 0 and velocity.x > 0
# DOWNHILL PRIORITY
if downhill_context and downhill_fakie:
downhill_fakie.visible = true
elif uphill_here:
uphill.visible = true
else:
if velocity.x >= 0:
_show_forward_roll()
else:
_show_fakie_roll()
else:
if downhill_context and downhill_fakie:
downhill_fakie.visible = true
else:
if velocity.x >= 0:
Ollie_1.visible = velocity.y < -100
Ollie_2.visible = velocity.y >= -100
else:
sprite_ollie_fakie.visible = velocity.y < -100
sprite_ollie_fakie_fall.visible = velocity.y >= -100
func _handle_kickflip(delta):
kickflip_timer += delta
if kickflip_timer >= 0.1:
kickflip_timer = 0
kickflip_frame += 1
_hide_all_sprites()
match kickflip_frame:
0: kickflip_1.visible = true
1: kickflip_2.visible = true
2: kickflip_3.visible = true
_: kickflip_active = false; _handle_normal_sprites()
func _handle_bs360(delta):
bs360_timer += delta
if bs360_timer >= 0.12:
bs360_timer = 0
bs360_frame += 1
_hide_all_sprites()
match bs360_frame:
0: bs360_1.visible = true
1: bs360_2.visible = true
2: bs360_3.visible = true
_: bs360_active = false; _handle_normal_sprites()
# ---------------- RAIL CHECK ----------------
func _is_on_rail_floor() -> bool:
if not is_on_floor():
return false
for i in range(get_slide_collision_count()):
var collision = get_slide_collision(i)
if collision.get_collider() == RailTilemap and collision.get_normal().y < -0.7:
return true
return false
# ---------------- GRIND SPARKS ----------------
func _handle_grind_sparks():
if collision_shape == null or grind_sparks.size() == 0:
return
var sparks_active := false
if is_on_floor():
for i in range(get_slide_collision_count()):
var collision = get_slide_collision(i)
if collision.get_collider() == RailTilemap:
sparks_active = true
break
spark_timer += get_process_delta_time()
if spark_timer >= spark_interval:
spark_timer = 0
spark_index = (spark_index + 1) % grind_sparks.size()
var bottom_pos = global_position + Vector2(0, collision_shape.shape.extents.y - 6)
var offset = Vector2((-13) if facing_right else (13), 0)
for i in range(grind_sparks.size()):
var s = grind_sparks[i]
s.global_position = bottom_pos + offset
s.visible = sparks_active and i == spark_index
s.flip_h = not facing_right
