Godot Version
v4.3.stable.official [77dcf97d8]
Question
I am making a 7 key Vertically-Scrolling Rhythm Game in Godot, and I am having struggles with certain aspects of the game. I added Long Notes, where you have to hold a key and release it at specific times.
I have a scene called LongNoteBeginning.tscn, and a scene called LongNoteEnding.tscn. When LongNoteBeginning enters the scene for the first time, it instantiates LongNoteEnding at a certain position, and when LongNoteEnding gets released, it gets deleted with queue_free()
.
My issue is that when I release a long note, the wrong LongNoteEnding gets deleted when there are multiple long notes on the screen. For example, look at this situation:
A: Long note beginning
B: Long note ending
C: Long note
XXXXBXX
XXXXCXX
XXXXCXX
XXXXAXX
XBXXXXX
XCXXXXX
XAXXXXX
In this case, when the first long note is released, either both LongNoteEndings are deleted, the second LongNoteEnding is deleted, or the first LongNoteEnding is deleted. I want only the first LongNoteEnding to be deleted.
Here are some scripts:
LongNoteBeginning.gd:
extends AnimatedSprite2D
# This list shows the X positions of all the lanes.
var laneData = [21.024, 51.024, 80.024, 115.024, 150.024, 180.024, 210.024]
@export var scroll_speed = 3
@export var lane = 1
@export var keybind = "Note1" # Can be from Note1 to Note7.
@export var noteScale = 5 # The scale of the long note.
@onready var hiteffect = load("res://Objects/Hiteffect/Hiteffect.tscn") # The hiteffect scene.
@onready var longNoteEnding = load("res://Objects/Notes/LongNoteEnding.tscn") # The long note end scene.
var currentStatus = "none" # This holds the current judgement of the note.
var freed = false # This variable prevents the code from running multiple times by checking if this variable is false before executing code. When the note gets deleted, this gets set to true.
var id = 0 # This variable determines whether or not it is the first in its queue.
var respectiveList = [] # This is the note queue for the note's lane (gets set in refreshQueue).
var scene_instance = null # Variable to hold the unique instance of long note ending
func deleteNote() -> void:
if freed:
return # Make sure that the note is not already freed before continuing.
freed = true # Sets freed to true to ensure no other script will be running.
if respectiveList.size() > 0:
respectiveList.remove_at(0) # Removes the note from the queue.
else:
print("deleteNote: Attempted to delete note ID: " + str(id) + " in lane: " + str(lane) + " but the queue is empty!")
# Safely free the instance
if is_instance_valid(scene_instance): # Check if the instance is valid.
scene_instance.queue_free()
# Safely free this note
queue_free()
func refreshQueue() -> void: # Updates the note's local queue based on its lane and respective queue in the global variables.
match lane:
1: respectiveList = PlayerGlobals.noteQueue1
2: respectiveList = PlayerGlobals.noteQueue2
3: respectiveList = PlayerGlobals.noteQueue3
4: respectiveList = PlayerGlobals.noteQueue4
5: respectiveList = PlayerGlobals.noteQueue5
6: respectiveList = PlayerGlobals.noteQueue6
7: respectiveList = PlayerGlobals.noteQueue7
func _ready() -> void:
global_position.x = laneData[lane - 1] # Sets the X based on its lane.
refreshQueue()
respectiveList.append(id) # Adds itself to the note queue.
scale.y = noteScale # Sets its scale.
# Create a unique instance of the long note ending.
scene_instance = longNoteEnding.instantiate()
scene_instance.global_position.y = global_position.y - ($Area2D/CollisionShape2D.global_position.y - global_position.y) - 10 # Some random ahhh formula I came up with while sleeping.
scene_instance.scroll_speed = scroll_speed
scene_instance.lane = lane
scene_instance.keybind = keybind
get_tree().get_current_scene().add_child(scene_instance)
# Set the appropriate animation based on the lane.
match lane:
1, 3, 5, 7: play("White")
2, 6: play("Blue")
4: play("Yellow")
func _process(_delta: float) -> void:
refreshQueue() # Ensures that the note queue is always updated.
global_position.y += 3.5 * PlayerGlobals.speed
if currentStatus == "miss" and not freed: # Adds a miss if the long note is not released.
PlayerGlobals.miss()
deleteNote()
func _input(_event):
if Input.is_action_just_pressed(keybind) and respectiveList.size() > 0 and respectiveList[0] == id and currentStatus != "none": # If the keybind is pressed, the note queue is not empty, the note is first in the queue, and the note is in the players range.
if freed: # Stop running the code if the note has already been freed.
return 0
match currentStatus:
"bad":
PlayerGlobals.bad()
PlayerGlobals.longNotes[lane - 1] = true # Idk if this is accurate to the original game. Keeping it anyways just in case.
"good":
PlayerGlobals.good()
PlayerGlobals.longNotes[lane - 1] = true
"cool":
PlayerGlobals.cool()
PlayerGlobals.longNotes[lane - 1] = true
var hiteffectInstance = hiteffect.instantiate() # Make the hiteffect.
hiteffectInstance.lane = lane
get_tree().get_current_scene().add_child(hiteffectInstance)
if PlayerGlobals.combo > PlayerGlobals.maxcombo:
PlayerGlobals.maxcombo = PlayerGlobals.combo # This sets maxcombo if the combo is a new highest.
deleteNote() # Finally, our work here is done. Delete the note.
LongNoteEnding.gd:
extends AnimatedSprite2D
# This list shows the X positions of all the lanes.
var laneData = [21.024, 51.024, 80.024, 115.024, 150.024, 180.024, 210.024]
@export var scroll_speed = 3
@export var lane = 1
@export var keybind = "Note1" # Can be from Note1 to Note7.
# The hiteffect scene.
@onready var hiteffect = load("res://Objects/Hiteffect/Hiteffect.tscn")
var currentStatus = "none" # This holds the current judgement of the note.
var freed = false # This variable prevents the code from running multiple times by checking if this variable is false before executing code. When the note gets deleted, this gets set to true.
var id = 0 # This variable determines whether or not it is the first in its queue.
var respectiveList = [] # This is the note queue for the note's lane (gets set in refreshQueue).
func deleteNote() -> void:
if freed:
return # Make sure that the note is not already freed before continuing.
freed = true # Sets freed to true to ensure no other script will be running.
queue_free()
if PlayerGlobals.longNotes[lane - 1]:
PlayerGlobals.longNotes[lane - 1] = false # Reset the long note status.
if respectiveList.size() > 0:
respectiveList.remove_at(0) # Removes the note from the queue.
else:
print("deleteNote: Attempted to delete note ID: " + str(id) + " in lane: " + str(lane) + " but the queue is empty!")
func refreshQueue() -> void: # Updates the note's local queue based on its lane and respective queue in the global variables.
match lane:
1: respectiveList = PlayerGlobals.noteQueue1
2: respectiveList = PlayerGlobals.noteQueue2
3: respectiveList = PlayerGlobals.noteQueue3
4: respectiveList = PlayerGlobals.noteQueue4
5: respectiveList = PlayerGlobals.noteQueue5
6: respectiveList = PlayerGlobals.noteQueue6
7: respectiveList = PlayerGlobals.noteQueue7
func _ready() -> void:
global_position.x = laneData[lane - 1] # Sets the X based on its lane.
refreshQueue()
respectiveList.append(id) # Adds itself to the note queue.
func _process(_delta: float) -> void:
refreshQueue() # Ensures that the note queue is always updated.
global_position.y += 3.5 * PlayerGlobals.speed
if currentStatus == "miss" and not freed: # Adds a miss if the long note is not released.
PlayerGlobals.miss()
deleteNote()
func _input(_event):
if Input.is_action_just_released(keybind) and respectiveList.size() > 0 and respectiveList[0] == id: # If the keybind is pressed, the note queue is not empty, and the note is first in the queue.
if freed:
return 0 # Stop running the code if the note has already been freed.
match currentStatus:
"bad":
PlayerGlobals.bad()
"good":
PlayerGlobals.good()
"cool":
PlayerGlobals.cool()
"none": # "none" is if you release it too late.
PlayerGlobals.miss()
var hiteffectInstance = hiteffect.instantiate() # Make the hiteffect.
hiteffectInstance.lane = lane
get_tree().get_current_scene().add_child(hiteffectInstance)
if PlayerGlobals.combo > PlayerGlobals.maxcombo:
PlayerGlobals.maxcombo = PlayerGlobals.combo # This sets maxcombo if the combo is a new highest.
deleteNote() # Finally, our work here is done. Delete the note.
Any help is appreciated. Thanks!
P.S. if there is anything in my code that is a bad practice or just not recommended, please let me know!