From 9ffd8d57c5d95ffc49abf7b2d7735d5f75eba1c2 Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Tue, 19 Nov 2019 20:08:35 +1030 Subject: [PATCH] Note judgement text --- GameTheme.gd | 12 ++++- InputHandler.gd | 2 +- Note.gd | 42 ++++++++++++---- NoteHandler.gd | 130 +++++++++++++++++++++++++++++++++++++++--------- Rules.gd | 9 ++++ main.tscn | 2 +- 6 files changed, 161 insertions(+), 36 deletions(-) diff --git a/GameTheme.gd b/GameTheme.gd index 9295f02..1ffd0f8 100644 --- a/GameTheme.gd +++ b/GameTheme.gd @@ -16,6 +16,8 @@ const JUDGE_TEXT_ANG3 := PI + JUDGE_TEXT_ANG2 const JUDGE_TEXT_ANG4 := -JUDGE_TEXT_ANG2 var judge_text_size2 := 0.5*judge_text_size/cos(JUDGE_TEXT_ANG2) +var judge_text_duration := 2.0 + # Color definitions const COLOR_TAP := Color(1, 0.15, 0.15, 1) const COLOR_TAP2 := Color(0.75, 0.5, 0, 1) # High-score taps ("breaks" in maimai) @@ -51,7 +53,7 @@ var RADIAL_UNIT_VECTORS := PoolVector2Array() # ideally const func init_radial_values(): for i in range(Rules.COLS): - var angle = deg2rad(fmod(Rules.FIRST_COLUMN_ANGLE_DEG + (i * Rules.COLS_ANGLE_DEG), 360.0)) + var angle = deg2rad(fposmod(Rules.FIRST_COLUMN_ANGLE_DEG + (i * Rules.COLS_ANGLE_DEG), 360.0)) RADIAL_COL_ANGLES.push_back(angle) RADIAL_UNIT_VECTORS.push_back(Vector2(cos(angle), sin(angle))) @@ -59,3 +61,11 @@ func init_radial_values(): func color_array_text(alpha: float) -> PoolColorArray: var color := Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, alpha) return PoolColorArray([color, color, color, color]) + +func color_array_tap(alpha: float, double:=false) -> PoolColorArray: + if alpha >= 1.0: + return COLOR_ARRAY_DOUBLE_4 if double else COLOR_ARRAY_TAP + else: + var col := COLOR_DOUBLE if double else COLOR_TAP + var color = Color(col.r, col.g, col.b, alpha) + return PoolColorArray([color, color, color, color]) \ No newline at end of file diff --git a/InputHandler.gd b/InputHandler.gd index 07f5ea9..d281393 100644 --- a/InputHandler.gd +++ b/InputHandler.gd @@ -27,7 +27,7 @@ func _init(): func _ready(): set_process_unhandled_input(true) # process user input set_fingers(0) - connect("button_pressed", self, "print_pressed") +# connect("button_pressed", self, "print_pressed") func print_pressed(col: int): diff --git a/Note.gd b/Note.gd index 872814d..624e51e 100644 --- a/Note.gd +++ b/Note.gd @@ -12,6 +12,28 @@ class NoteBase: var time_death: float var column: int var double_hit := false + var time_activated := INF + var missed := false + +class NoteTap extends NoteBase: + var type := NOTE_TAP + func _init(time_hit: float, column: int): + self.time_hit = time_hit + self.time_death = time_hit + DEATH_DELAY + self.column = column + +class NoteHold extends NoteBase: + var type := NOTE_HOLD + var time_release: float + var duration: float + var is_held: bool + func _init(time_hit: float, duration: float, column: int): + self.time_hit = time_hit + self.duration = duration + self.time_release = time_hit + duration + self.time_death = time_release + DEATH_DELAY + self.column = column + self.is_held = false class NoteSlide extends NoteBase: var type := NOTE_SLIDE @@ -84,20 +106,20 @@ class NoteSlide extends NoteBase: -static func make_tap(time_hit: float, column: int) -> Dictionary: - return {type=NOTE_TAP, time_hit=time_hit, time_death=time_hit+DEATH_DELAY, column=column, double_hit=false} +static func make_tap(time_hit: float, column: int) -> NoteTap: +# return {type=NOTE_TAP, time_hit=time_hit, time_death=time_hit+DEATH_DELAY, column=column, double_hit=false} + return NoteTap.new(time_hit, column) -static func make_break(time_hit: float, column: int) -> Dictionary: - return {type=NOTE_TAP, time_hit=time_hit, time_death=time_hit+DEATH_DELAY, column=column, double_hit=false} +static func make_break(time_hit: float, column: int): # -> Dictionary: +# return {type=NOTE_TAP, time_hit=time_hit, time_death=time_hit+DEATH_DELAY, column=column, double_hit=false} + return NoteTap.new(time_hit, column) -static func make_hold(time_hit: float, duration: float, column: int) -> Dictionary: - var time_release := time_hit + duration - return {type=NOTE_HOLD, time_hit=time_hit, time_release=time_release, time_death=time_release+DEATH_DELAY, column=column, double_hit=false} +static func make_hold(time_hit: float, duration: float, column: int) -> NoteHold: +# var time_release := time_hit + duration +# return {type=NOTE_HOLD, time_hit=time_hit, time_release=time_release, time_death=time_release+DEATH_DELAY, column=column, double_hit=false} + return NoteHold.new(time_hit, duration, column) static func make_slide(time_hit: float, duration: float, column: int, column_release: int, slide_type:=SlideType.CHORD) -> NoteSlide: -# var time_release := time_hit + duration -# return {type=NOTE_SLIDE, time_hit=time_hit, time_release=time_release, duration=duration, -# time_death=time_release+DEATH_DELAY, column=column, column_release=column_release, double_hit=false} return NoteSlide.new(time_hit, duration, column, column_release, slide_type) static func make_touch(time_hit: float, location: Vector2) -> Dictionary: diff --git a/NoteHandler.gd b/NoteHandler.gd index 7e13462..2147fe5 100644 --- a/NoteHandler.gd +++ b/NoteHandler.gd @@ -28,11 +28,14 @@ var bpm := 120.0 var sync_offset_video := 0.0 # Time in seconds to the first beat var sync_offset_audio := 0.0 # Time in seconds to the first beat var active_notes := [] +var active_judgement_texts := [] var all_notes := [] var next_note_to_load := 0 var slide_trail_meshes := {} var slide_trail_mesh_instances := {} +var noteline_array_image := Image.new() + # UV vertex arrays for our sprites # tap/star/arrow are 4-vertex 2-triangle simple squares # hold is 8-vertex 6-triangle to enable stretching in the middle @@ -65,6 +68,15 @@ func make_text_UVs(): text_UV_arrays.append(make_text_UV(row, column)) enum TextStyle {STRAIGHT=0, ARC=1, ARC_EARLY=2, ARC_LATE=3} enum TextWord {NICE=0, OK=4, NG=8, PERFECT=12, GREAT=16, GOOD=20, ALMOST=24, MISS=28} +const TextJudgement := { + 0: TextWord.PERFECT + TextStyle.ARC, + 1: TextWord.GREAT + TextStyle.ARC_LATE, + -1: TextWord.GREAT + TextStyle.ARC_EARLY, + 2: TextWord.GOOD + TextStyle.ARC_LATE, + -2: TextWord.GOOD + TextStyle.ARC_EARLY, + 3: TextWord.ALMOST + TextStyle.ARC_LATE, + -3: TextWord.ALMOST + TextStyle.ARC_EARLY, +} func make_text_mesh(mesh: ArrayMesh, text_id: int, pos: Vector2, angle: float, alpha:=1.0, scale:=1.0): var r := GameTheme.judge_text_size2 * scale @@ -83,8 +95,8 @@ func make_text_mesh(mesh: ArrayMesh, text_id: int, pos: Vector2, angle: float, a func make_judgement_text(mesh: ArrayMesh, text_id: int, col: int, progress:=0.0): make_text_mesh(mesh, text_id, - GameTheme.RADIAL_UNIT_VECTORS[col] * GameTheme.receptor_ring_radius * lerp(0.85, 0.8, progress), - GameTheme.RADIAL_COL_ANGLES[col]-PI/2.0, lerp(1.0, 0.0, progress), lerp(1.0, 0.5, progress) + GameTheme.RADIAL_UNIT_VECTORS[col] * GameTheme.receptor_ring_radius * lerp(0.85, 0.85*0.75, progress), + GameTheme.RADIAL_COL_ANGLES[col]-PI/2.0, lerp(1.0, 0.0, progress), lerp(1.0, 0.75, progress) ) # ---------------------------------------------------------------------------------------------------------------------------------------------------- @@ -208,14 +220,63 @@ func make_slide_trail_mesh(note) -> ArrayMesh: mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) return mesh +#---------------------------------------------------------------------------------------------------------------------------------------------- +func activate_note(note, judgement): + active_judgement_texts.append({col=note.column, judgement=judgement, time=t}) + + note.time_activated = t + match note.type: + Note.NOTE_HOLD: + note.is_held = true + Note.NOTE_SLIDE: + pass # Set up slide trail? + return + +func button_pressed(col): + for note in active_notes: + if note.column != col: + continue + if note.time_activated != INF: + continue + var hit_delta = t - note.time_hit + if hit_delta >= 0.0: + if hit_delta > Rules.JUDGEMENT_TIMES_POST[-1]: + continue # missed + for i in Rules.JUDGEMENT_TIERS: + if hit_delta <= Rules.JUDGEMENT_TIMES_POST[i]: + activate_note(note, i) + return + else: + if -hit_delta > Rules.JUDGEMENT_TIMES_PRE[-1]: + continue # too far away + for i in Rules.JUDGEMENT_TIERS: + if -hit_delta <= Rules.JUDGEMENT_TIMES_POST[i]: + activate_note(note, -i) + return + +func touchbutton_pressed(col): + button_pressed(col) + +func check_hold_release(col): + for note in active_notes: + if note.column != col: + continue + if note.type == Note.NOTE_HOLD: + if note.is_held == true: + note.is_held = false + pass + +func button_released(col): + # We only care about hold release. + # For that particular case, we want both to be unheld. + if $"/root/main/InputHandler".touchbuttons_pressed[col] == 0: + check_hold_release(col) + +func touchbutton_released(col): + if $"/root/main/InputHandler".buttons_pressed[col] == 0: + check_hold_release(col) #---------------------------------------------------------------------------------------------------------------------------------------------- - -func _init(): - Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN) - GameTheme.init_radial_values() - make_text_UVs() - func _draw(): var mesh := ArrayMesh.new() var noteline_data : Image = noteline_array_image.get_rect(Rect2(0, 0, 16, 16)) @@ -234,10 +295,16 @@ func _draw(): var note_center = (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position * GameTheme.receptor_ring_radius) match note.type: Note.NOTE_TAP: - var color = GameTheme.COLOR_ARRAY_DOUBLE_4 if note.double_hit else GameTheme.COLOR_ARRAY_TAP + var color: PoolColorArray + if note.time_activated == INF: + color = GameTheme.color_array_tap(1.0, note.double_hit) + else: + color = GameTheme.color_array_tap(lerp(1.0, 0.0, (note.time_death-t)/Note.DEATH_DELAY), note.double_hit) make_tap_mesh(mesh, note_center, scale, color) Note.NOTE_HOLD: var color = GameTheme.COLOR_ARRAY_DOUBLE_8 if note.double_hit else GameTheme.COLOR_ARRAY_HOLD + if note.is_held: + color = GameTheme.COLOR_ARRAY_HOLD_HELD var position_rel : float = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats if position_rel > 0: var note_rel_center := (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position_rel * GameTheme.receptor_ring_radius) @@ -272,21 +339,25 @@ func _draw(): $notelines.set_texture(noteline_data_tex) $meshinstance.set_mesh(mesh) - - var textmesh := ArrayMesh.new() - make_judgement_text(textmesh, TextWord.PERFECT+TextStyle.ARC, 0, 0.0) - make_judgement_text(textmesh, TextWord.GREAT+TextStyle.ARC_LATE, 1, 0.0) - make_judgement_text(textmesh, TextWord.GOOD+TextStyle.ARC_EARLY, 2, 0.1) - make_judgement_text(textmesh, TextWord.ALMOST+TextStyle.ARC_LATE, 3, 0.2) - make_judgement_text(textmesh, TextWord.MISS+TextStyle.ARC, 4, 0.4) - make_judgement_text(textmesh, TextWord.NICE+TextStyle.ARC, 5, 0.6) - make_judgement_text(textmesh, TextWord.OK+TextStyle.ARC, 6, 0.8) - make_judgement_text(textmesh, TextWord.NG+TextStyle.ARC, 7, 0.9) - $JudgeText.set_mesh(textmesh) - # draw_mesh(mesh, tex) -var noteline_array_image := Image.new() + var textmesh := ArrayMesh.new() +# make_judgement_text(textmesh, TextWord.PERFECT+TextStyle.ARC, 0, fmod(t, 1.0)) +# make_judgement_text(textmesh, TextWord.GREAT+TextStyle.ARC_LATE, 1, ease(fmod(t, 1.0), 1.25)) +# make_judgement_text(textmesh, TextWord.GOOD+TextStyle.ARC_EARLY, 2, clamp(fmod(t, 2.0)-1, 0, 1)) +# make_judgement_text(textmesh, TextWord.ALMOST+TextStyle.ARC_LATE, 3, ease(clamp(fmod(t, 2.0)-1, 0, 1), 1.25)) +# make_judgement_text(textmesh, TextWord.MISS+TextStyle.ARC, 4, clamp(fmod(t, 2.0)-0.5, 0, 1)) +# make_judgement_text(textmesh, TextWord.NICE+TextStyle.ARC, 5, ease(clamp(fmod(t, 2.0)-0.5, 0, 1), 1.25)) +# make_judgement_text(textmesh, TextWord.OK+TextStyle.ARC, 6, fmod(t, 2.0)*0.5) +# make_judgement_text(textmesh, TextWord.NG+TextStyle.ARC, 7, ease(fmod(t, 2.0)*0.5, 1.25)) + for text in active_judgement_texts: + make_judgement_text(textmesh, TextJudgement[text.judgement], text.col, (t-text.time)/GameTheme.judge_text_duration) + $JudgeText.set_mesh(textmesh) + +func _init(): + Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN) + GameTheme.init_radial_values() + make_text_UVs() # Called when the node enters the scene tree for the first time. func _ready(): @@ -325,7 +396,8 @@ func _ready(): noteline_array_image.fill(Color(0.0, 0.0, 0.0)) # Format: first 15 rows are for hit events, last row is for releases only (no ring glow) - all_notes = FileLoader.SRT.load_file("res://songs/199_cirno_master.srt") +# all_notes = FileLoader.SRT.load_file("res://songs/199_cirno_master.srt") + all_notes = FileLoader.SRT.load_file("res://songs/199_cirno_adv.srt") bpm = 175.0 sync_offset_audio = 0.553 sync_offset_video = 0.553 @@ -336,6 +408,13 @@ func _ready(): if note.type == Note.NOTE_SLIDE: slide_trail_meshes[note.slide_id] = make_slide_trail_mesh(note) + $"/root/main/InputHandler".connect("button_pressed", self, "button_pressed") + $"/root/main/InputHandler".connect("touchbutton_pressed", self, "touchbutton_pressed") + $"/root/main/InputHandler".connect("button_released", self, "button_released") + $"/root/main/InputHandler".connect("touchbutton_released", self, "touchbutton_released") + + + func game_time(realtime: float) -> float: return time * bpm / 60.0 @@ -373,6 +452,11 @@ func _process(delta): slide_trail_mesh_instances.erase(note.slide_id) active_notes.remove(i) + # Clean out expired judgement texts + # By design they will always be in order so we can ignore anything past the first index + while (len(active_judgement_texts) > 0) and ((t-active_judgement_texts[0].time) > GameTheme.judge_text_duration): + active_judgement_texts.pop_front() + # Add new notes as necessary while true: if next_note_to_load >= len(all_notes): diff --git a/Rules.gd b/Rules.gd index 373c59c..2f586ba 100644 --- a/Rules.gd +++ b/Rules.gd @@ -6,3 +6,12 @@ const COLS_ANGLE_RAD := COLS_ANGLE_DEG * TAU/360.0 # deg2rad isn't a const func const FIRST_COLUMN_ANGLE_DEG := (COLS_ANGLE_DEG/2.0 if !(COLS%2) else 0.0) - 90.0 #-67.5 const COLS_TOUCH_ARC_DEG := 240.0/COLS + +const JUDGEMENT_STRINGS := ["Perfect", "Great", "Good", "Almost"] +const JUDGEMENT_TIERS := 4 +const JUDGEMENT_TIMES_PRE := [0.040, 0.090, 0.135, 0.180] +const JUDGEMENT_TIMES_POST := [0.040, 0.090, 0.135, 0.180] +const JUDGEMENT_TIMES_RELEASE_PRE := [0.050, 0.090, 0.135, 0.180] +const JUDGEMENT_TIMES_RELEASE_POST := [0.100, 0.140, 0.155, 0.230] # Small grace period +const JUDGEMENT_TIMES_SLIDE_PRE := [0.090, 0.135, 0.180, 0.230] # Small grace period, sort-of +const JUDGEMENT_TIMES_SLIDE_POST := [0.090, 0.135, 0.180, 0.230] \ No newline at end of file diff --git a/main.tscn b/main.tscn index f7c56b5..db660cb 100644 --- a/main.tscn +++ b/main.tscn @@ -49,7 +49,7 @@ grow_vertical = 2 rect_pivot_offset = Vector2( 540, 540 ) mouse_filter = 2 stream = ExtResource( 2 ) -volume_db = -11.6 +volume_db = -16.62 script = ExtResource( 3 ) __meta__ = { "_edit_use_anchors_": false