commit 8aa139646e9d4a2c934e7eeab3bd6c548a412657 Author: Luke Hubmayer-Werner Date: Sun Nov 10 15:09:14 2019 +1030 Project files (no assets/song files) diff --git a/.import/ball.png-ca93c4f2e7b7f8325cbd933673092ff4.md5 b/.import/ball.png-ca93c4f2e7b7f8325cbd933673092ff4.md5 new file mode 100644 index 0000000..f7e67bf --- /dev/null +++ b/.import/ball.png-ca93c4f2e7b7f8325cbd933673092ff4.md5 @@ -0,0 +1,3 @@ +source_md5="32472f2c19639a6447e7a0cff0b3aa9c" +dest_md5="ebce88a89ad9ff81c75951272911a33b" + diff --git a/.import/ball.png-ca93c4f2e7b7f8325cbd933673092ff4.stex b/.import/ball.png-ca93c4f2e7b7f8325cbd933673092ff4.stex new file mode 100644 index 0000000..dfd985b Binary files /dev/null and b/.import/ball.png-ca93c4f2e7b7f8325cbd933673092ff4.stex differ diff --git a/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.md5 b/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.md5 new file mode 100644 index 0000000..f75f542 --- /dev/null +++ b/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.md5 @@ -0,0 +1,3 @@ +source_md5="8dd9ff1eebf38898a54579d8c01b0a88" +dest_md5="da70afec3c66d4e872db67f808e12edb" + diff --git a/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex b/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex new file mode 100644 index 0000000..46e6d2a Binary files /dev/null and b/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex differ diff --git a/.import/spritesheet-1024.png-911fbbbbf60eae7217ccd79a879d1686.md5 b/.import/spritesheet-1024.png-911fbbbbf60eae7217ccd79a879d1686.md5 new file mode 100644 index 0000000..23e81b8 --- /dev/null +++ b/.import/spritesheet-1024.png-911fbbbbf60eae7217ccd79a879d1686.md5 @@ -0,0 +1,3 @@ +source_md5="3573d33760ed5048bff51bb785b1356b" +dest_md5="67ba11a5d30fd946675bfac25802bd94" + diff --git a/.import/spritesheet-1024.png-911fbbbbf60eae7217ccd79a879d1686.stex b/.import/spritesheet-1024.png-911fbbbbf60eae7217ccd79a879d1686.stex new file mode 100644 index 0000000..969436b Binary files /dev/null and b/.import/spritesheet-1024.png-911fbbbbf60eae7217ccd79a879d1686.stex differ diff --git a/.import/spritesheet-1024.png-b98defd5d8caae46c768f7bc615ffa47.md5 b/.import/spritesheet-1024.png-b98defd5d8caae46c768f7bc615ffa47.md5 new file mode 100644 index 0000000..23e81b8 --- /dev/null +++ b/.import/spritesheet-1024.png-b98defd5d8caae46c768f7bc615ffa47.md5 @@ -0,0 +1,3 @@ +source_md5="3573d33760ed5048bff51bb785b1356b" +dest_md5="67ba11a5d30fd946675bfac25802bd94" + diff --git a/.import/spritesheet-1024.png-b98defd5d8caae46c768f7bc615ffa47.stex b/.import/spritesheet-1024.png-b98defd5d8caae46c768f7bc615ffa47.stex new file mode 100644 index 0000000..969436b Binary files /dev/null and b/.import/spritesheet-1024.png-b98defd5d8caae46c768f7bc615ffa47.stex differ diff --git a/.import/spritesheet-512.png-0cf1a320bb155cf0e8cf4c6238930d56.md5 b/.import/spritesheet-512.png-0cf1a320bb155cf0e8cf4c6238930d56.md5 new file mode 100644 index 0000000..e27bea3 --- /dev/null +++ b/.import/spritesheet-512.png-0cf1a320bb155cf0e8cf4c6238930d56.md5 @@ -0,0 +1,3 @@ +source_md5="e00f36b6f082fa699e2b7a926ab510e1" +dest_md5="a5c53f0952668b57c4b2a19a4373e232" + diff --git a/.import/spritesheet-512.png-0cf1a320bb155cf0e8cf4c6238930d56.stex b/.import/spritesheet-512.png-0cf1a320bb155cf0e8cf4c6238930d56.stex new file mode 100644 index 0000000..14ad817 Binary files /dev/null and b/.import/spritesheet-512.png-0cf1a320bb155cf0e8cf4c6238930d56.stex differ diff --git a/.import/spritesheet-512.png-6ccc43083820b92018a3862707b08cb4.md5 b/.import/spritesheet-512.png-6ccc43083820b92018a3862707b08cb4.md5 new file mode 100644 index 0000000..e27bea3 --- /dev/null +++ b/.import/spritesheet-512.png-6ccc43083820b92018a3862707b08cb4.md5 @@ -0,0 +1,3 @@ +source_md5="e00f36b6f082fa699e2b7a926ab510e1" +dest_md5="a5c53f0952668b57c4b2a19a4373e232" + diff --git a/.import/spritesheet-512.png-6ccc43083820b92018a3862707b08cb4.stex b/.import/spritesheet-512.png-6ccc43083820b92018a3862707b08cb4.stex new file mode 100644 index 0000000..14ad817 Binary files /dev/null and b/.import/spritesheet-512.png-6ccc43083820b92018a3862707b08cb4.stex differ diff --git a/.import/spritesheet.png-59f7dfdebfc035c80a63445e3bd8a9bd.md5 b/.import/spritesheet.png-59f7dfdebfc035c80a63445e3bd8a9bd.md5 new file mode 100644 index 0000000..4ff3415 --- /dev/null +++ b/.import/spritesheet.png-59f7dfdebfc035c80a63445e3bd8a9bd.md5 @@ -0,0 +1,3 @@ +source_md5="4357e1186d4059f7d05835592f280597" +dest_md5="18b79f19f1bbd977bc9f61b8d7bfb093" + diff --git a/.import/spritesheet.png-59f7dfdebfc035c80a63445e3bd8a9bd.stex b/.import/spritesheet.png-59f7dfdebfc035c80a63445e3bd8a9bd.stex new file mode 100644 index 0000000..2d6efc5 Binary files /dev/null and b/.import/spritesheet.png-59f7dfdebfc035c80a63445e3bd8a9bd.stex differ diff --git a/.import/spritesheet.svg-8dd3a9856197e408bad66877e1339058.md5 b/.import/spritesheet.svg-8dd3a9856197e408bad66877e1339058.md5 new file mode 100644 index 0000000..6e1caef --- /dev/null +++ b/.import/spritesheet.svg-8dd3a9856197e408bad66877e1339058.md5 @@ -0,0 +1,3 @@ +source_md5="23af4b0ba3693869627fea51d208c99f" +dest_md5="f3e47875966345065cd62fcde1255e7b" + diff --git a/.import/spritesheet.svg-8dd3a9856197e408bad66877e1339058.stex b/.import/spritesheet.svg-8dd3a9856197e408bad66877e1339058.stex new file mode 100644 index 0000000..03dbf8f Binary files /dev/null and b/.import/spritesheet.svg-8dd3a9856197e408bad66877e1339058.stex differ diff --git a/.import/spritesheet.svg-9525df2f4bc829b0dad6f6fd6ca0bdd6.md5 b/.import/spritesheet.svg-9525df2f4bc829b0dad6f6fd6ca0bdd6.md5 new file mode 100644 index 0000000..6e1caef --- /dev/null +++ b/.import/spritesheet.svg-9525df2f4bc829b0dad6f6fd6ca0bdd6.md5 @@ -0,0 +1,3 @@ +source_md5="23af4b0ba3693869627fea51d208c99f" +dest_md5="f3e47875966345065cd62fcde1255e7b" + diff --git a/.import/spritesheet.svg-9525df2f4bc829b0dad6f6fd6ca0bdd6.stex b/.import/spritesheet.svg-9525df2f4bc829b0dad6f6fd6ca0bdd6.stex new file mode 100644 index 0000000..03dbf8f Binary files /dev/null and b/.import/spritesheet.svg-9525df2f4bc829b0dad6f6fd6ca0bdd6.stex differ diff --git a/Bezel.gd b/Bezel.gd new file mode 100644 index 0000000..b26bff5 --- /dev/null +++ b/Bezel.gd @@ -0,0 +1,27 @@ +extends "res://main.gd" + +# Draw the bezel for radial gamemode + +func _draw(): + var bezel_color := Color.black + var bezel_colors := PoolColorArray([bezel_color]) + var bezel_points: PoolVector2Array + + draw_rect(Rect2(0, 0, x_margin, screen_height), bezel_color) + draw_rect(Rect2(1920-x_margin, 0, x_margin, screen_height), bezel_color) + + bezel_points = arc_point_list(screen_center, screen_height/2, 0, 90) + bezel_points.push_back(Vector2(1920-x_margin, 0)) + draw_polygon(bezel_points, bezel_colors) + + bezel_points = arc_point_list(screen_center, screen_height/2, 90, 180) + bezel_points.push_back(Vector2(x_margin, 0)) + draw_polygon(bezel_points, bezel_colors) + + bezel_points = arc_point_list(screen_center, screen_height/2, 180, 270) + bezel_points.push_back(Vector2(x_margin, screen_height)) + draw_polygon(bezel_points, bezel_colors) + + bezel_points = arc_point_list(screen_center, screen_height/2, 270, 360) + bezel_points.push_back(Vector2(1920-x_margin, screen_height)) + draw_polygon(bezel_points, bezel_colors) diff --git a/FileLoader.gd b/FileLoader.gd new file mode 100644 index 0000000..b0ea617 --- /dev/null +++ b/FileLoader.gd @@ -0,0 +1,46 @@ +#extends Object +extends Node + +class SRT: + const TAP_DURATION := 0.062500 + const ID_BREAK := 4 + const ID_HOLD := 2 + const ID_SLIDE_END := 128 + + static func load_file(filename): + var file = File.new() + var err = file.open(filename, File.READ) + if err != OK: + print(err) + return err + var notes = [] + var beats_per_measure := 4 + var length = file.get_len() + while (file.get_position() < (length-2)): + var noteline = file.get_csv_line() + var time_hit := (float(noteline[0]) + float(noteline[1])) * beats_per_measure + var duration := float(noteline[2]) * beats_per_measure + var column := int(noteline[3]) + var id := int(noteline[4]) + var id2 := int(noteline[5]) + var id3 := int(noteline[6]) + + match id: + ID_HOLD: + notes.push_back(Note.make_hold(time_hit, duration, column)) + ID_BREAK: + notes.push_back(Note.make_break(time_hit, column)) + ID_SLIDE_END: + pass # id2 is slide ID + _: + if id2 == 0: + notes.push_back(Note.make_tap(time_hit, column)) + else: + # id2 is slide ID, id3 is slide pattern + # In order to properly declare the slide, we need the paired endcap which may not be the next note + notes.push_back(Note.make_slide(time_hit, duration, column, column)) + return notes + +class SRB: + func load_file(filename): + pass diff --git a/Note.gd b/Note.gd new file mode 100644 index 0000000..e754064 --- /dev/null +++ b/Note.gd @@ -0,0 +1,37 @@ +#extends Object +extends Node + +#class_name Note + +enum {NOTE_TAP, NOTE_HOLD, NOTE_SLIDE, NOTE_ARROW, NOTE_TOUCH, NOTE_TOUCH_HOLD} +const DEATH_DELAY := 0.45 + +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_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_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_slide(time_hit: float, duration: float, column: int, column_release: int) -> Dictionary: + var time_release := time_hit + duration + return {type=NOTE_SLIDE, time_hit=time_hit, time_release=time_release, + time_death=time_release+DEATH_DELAY, column=column, column_release=column_release, double_hit=false} + +static func make_touch(time_hit: float, location: Vector2) -> Dictionary: + return {type=NOTE_TOUCH, time_hit=time_hit, time_death=time_hit+DEATH_DELAY, location=location, double_hit=false} + +static func make_touch_hold(time_hit: float, duration: float, location: Vector2) -> Dictionary: + var time_release := time_hit + duration + return {type=NOTE_TOUCH_HOLD, time_hit=time_hit, time_release=time_release, time_death=time_release+DEATH_DELAY, location=location, double_hit=false} + +static func process_doubles(note_array: Array): + # Preprocess double hits + if len(note_array): + for i in range(len(note_array)-1): + if note_array[i].time_hit == note_array[i+1].time_hit: + note_array[i].double_hit = true + note_array[i+1].double_hit = true diff --git a/NoteHandler.gd b/NoteHandler.gd new file mode 100644 index 0000000..6060087 --- /dev/null +++ b/NoteHandler.gd @@ -0,0 +1,393 @@ +extends "res://main.gd" + +# This script will draw all note events. + +var tex := preload("res://assets/spritesheet-1024.png") + +const first_column_angle_deg := -67.5 +var radial_col_angles := PoolRealArray() +var radial_unit_vectors := PoolVector2Array() + +const RING_LINE_SEGMENTS_PER_COLUMN := 12 +var RING_LINE_SEGMENTS_VECTORS := PoolVector2Array() + +const cols := 8 +const cols_angle := 360.0/cols +const ring_segs := cols * RING_LINE_SEGMENTS_PER_COLUMN +const ring_seg_angle := 360.0/ring_segs + +var sprite_size := 128 +var sprite_size2 := sprite_size/2 +const INNER_NOTE_CIRCLE_RATIO := 0.3 +const SQRT2 := sqrt(2) +const DEG45 := deg2rad(45.0) +const DEG90 := deg2rad(90.0) +const DEG135 := deg2rad(135.0) + +var time := 0.0 +var t := 0.0 +var bpm := 120.0 +var note_forecast_beats := 2.0 +var active_notes := [] +var all_notes := [] +var next_note_to_load := 0 + +# 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 +const UV_ARRAY_TAP := PoolVector2Array([Vector2(0, 0.5), Vector2(0.5, 0.5), Vector2(0, 1), Vector2(0.5, 1)]) +const UV_ARRAY_HOLD := PoolVector2Array([ + Vector2(0.5, 0.5), Vector2(1, 0.5), Vector2(0.5, 0.75), Vector2(1, 0.75), + Vector2(0.5, 0.75), Vector2(1, 0.75), Vector2(0.5, 1), Vector2(1, 1) + ]) +const UV_ARRAY_STAR := PoolVector2Array([Vector2(0.5, 0), Vector2(1, 0), Vector2(0.5, 0.5), Vector2(1, 0.5)]) +const UV_ARRAY_ARROW := PoolVector2Array([Vector2(0, 0), Vector2(0.5, 0), Vector2(0, 0.5), Vector2(0.5, 0.5)]) + +# Normal vertex arrays for our sprites +const DEFAULT_NORMAL := Vector3(0, 0, 1) +var NORMAL_ARRAY_4 := PoolVector3Array([DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL]) +var NORMAL_ARRAY_8 := PoolVector3Array([ + DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL, + DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL + ]) + +# 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) +const COLOR_HOLD := Color(1, 0.15, 0.15, 1) +const COLOR_HOLD_HELD := Color(1, 1, 1, 1) +const COLOR_STAR := Color(0, 0, 1, 1) +const COLOR_DOUBLE := Color(1, 1, 0, 1) # When two (or more in master) hit events coincide + +var COLOR_ARRAY_TAP := PoolColorArray([COLOR_TAP, COLOR_TAP, COLOR_TAP, COLOR_TAP]) +var COLOR_ARRAY_TAP2 := PoolColorArray([COLOR_TAP2, COLOR_TAP2, COLOR_TAP2, COLOR_TAP2]) +var COLOR_ARRAY_HOLD := PoolColorArray([ + COLOR_HOLD, COLOR_HOLD, COLOR_HOLD, COLOR_HOLD, + COLOR_HOLD, COLOR_HOLD, COLOR_HOLD, COLOR_HOLD + ]) +var COLOR_ARRAY_HOLD_HELD := PoolColorArray([ + COLOR_HOLD_HELD, COLOR_HOLD_HELD, COLOR_HOLD_HELD, COLOR_HOLD_HELD, + COLOR_HOLD_HELD, COLOR_HOLD_HELD, COLOR_HOLD_HELD, COLOR_HOLD_HELD + ]) +var COLOR_ARRAY_STAR := PoolColorArray([COLOR_STAR, COLOR_STAR, COLOR_STAR, COLOR_STAR]) +var COLOR_ARRAY_DOUBLE_4 := PoolColorArray([COLOR_DOUBLE, COLOR_DOUBLE, COLOR_DOUBLE, COLOR_DOUBLE]) +var COLOR_ARRAY_DOUBLE_8 := PoolColorArray([ + COLOR_DOUBLE, COLOR_DOUBLE, COLOR_DOUBLE, COLOR_DOUBLE, + COLOR_DOUBLE, COLOR_DOUBLE, COLOR_DOUBLE, COLOR_DOUBLE + ]) + +# Helper functions to generate meshes from vertex arrays +func make_tap_mesh(mesh: ArrayMesh, vertex_array, color_array = COLOR_ARRAY_TAP): + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_NORMAL] = NORMAL_ARRAY_4 + arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_TAP + arrays[Mesh.ARRAY_COLOR] = color_array + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + +func make_hold_mesh(mesh: ArrayMesh, vertex_array, color_array = COLOR_ARRAY_HOLD): + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_NORMAL] = NORMAL_ARRAY_8 + arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_HOLD + arrays[Mesh.ARRAY_COLOR] = color_array + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + +func make_star_mesh(mesh: ArrayMesh, vertex_array, color_array = COLOR_ARRAY_STAR): + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_NORMAL] = NORMAL_ARRAY_4 + arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_STAR + arrays[Mesh.ARRAY_COLOR] = color_array + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + +func make_arrow_mesh(mesh: ArrayMesh, vertex_array, color_array = COLOR_ARRAY_TAP): + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array + arrays[Mesh.ARRAY_NORMAL] = NORMAL_ARRAY_4 + arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_ARROW + arrays[Mesh.ARRAY_COLOR] = color_array + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + + +func make_tap_note(mesh: ArrayMesh, column: int, position: float, scale := 1.0, color_array := COLOR_ARRAY_TAP) -> ArrayMesh: + if position < INNER_NOTE_CIRCLE_RATIO: + scale *= position/INNER_NOTE_CIRCLE_RATIO + position = INNER_NOTE_CIRCLE_RATIO + var note_center = screen_center + (radial_unit_vectors[column] * position * receptor_ring_radius) + var dim = sprite_size2 * scale + var vertices = PoolVector2Array([note_center + Vector2(-dim, -dim), note_center + Vector2(dim, -dim), note_center + Vector2(-dim, dim), note_center + Vector2(dim, dim)]) + make_tap_mesh(mesh, vertices, color_array) + return mesh + +func make_hold_note(mesh: ArrayMesh, column: int, position1: float, position2: float, scale := 1.0, color_array = COLOR_ARRAY_HOLD) -> ArrayMesh: + if position1 < INNER_NOTE_CIRCLE_RATIO: + scale *= position1/INNER_NOTE_CIRCLE_RATIO + position1 = INNER_NOTE_CIRCLE_RATIO + if position2 < INNER_NOTE_CIRCLE_RATIO: + position2 = INNER_NOTE_CIRCLE_RATIO + var note_center1 = screen_center + (radial_unit_vectors[column] * position1 * receptor_ring_radius) + var note_center2 = screen_center + (radial_unit_vectors[column] * position2 * receptor_ring_radius) + var dim = sprite_size2 * scale + var dim2 = dim * SQRT2 + var angle = radial_col_angles[column] + var a1 = angle - DEG45 + var a2 = angle + DEG45 + var a3 = angle - DEG90 + var a4 = angle + DEG90 + var a5 = angle - DEG135 + var a6 = angle + DEG135 + var vertices = PoolVector2Array([ + note_center1 + dim2*Vector2(cos(a1), sin(a1)), note_center1 + dim2*Vector2(cos(a2), sin(a2)), + note_center1 + dim*Vector2(cos(a3), sin(a3)), note_center1 + dim*Vector2(cos(a4), sin(a4)), + note_center2 + dim*Vector2(cos(a3), sin(a3)), note_center2 + dim*Vector2(cos(a4), sin(a4)), + note_center2 + dim2*Vector2(cos(a5), sin(a5)), note_center2 + dim2*Vector2(cos(a6), sin(a6)) + ]) + make_hold_mesh(mesh, vertices, color_array) + return mesh + +func make_slide_note(mesh: ArrayMesh, column: int, position: float, scale := 1.0, color_array := COLOR_ARRAY_STAR) -> ArrayMesh: + if position < INNER_NOTE_CIRCLE_RATIO: + scale *= position/INNER_NOTE_CIRCLE_RATIO + position = INNER_NOTE_CIRCLE_RATIO + var note_center = screen_center + (radial_unit_vectors[column] * position * receptor_ring_radius) + var dim = sprite_size2 * scale * SQRT2 + var angle = deg2rad(fmod(t*270.0, 360.0)) + var a1 = angle - DEG45 + var a2 = angle + DEG45 + var a3 = angle - DEG135 + var a4 = angle + DEG135 + var vertices = PoolVector2Array([ + note_center + dim*Vector2(cos(a1), sin(a1)), note_center + dim*Vector2(cos(a2), sin(a2)), + note_center + dim*Vector2(cos(a3), sin(a3)), note_center + dim*Vector2(cos(a4), sin(a4)) + ]) + make_star_mesh(mesh, vertices, color_array) + return mesh + +var ring_line_segments_alphas = PoolRealArray() +var ring_line_segments_widths = PoolRealArray() +func _init(): + Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN) + for i in range(cols): + var angle = deg2rad(first_column_angle_deg + (i * cols_angle)) + radial_col_angles.push_back(angle) + radial_unit_vectors.push_back(Vector2(cos(angle), sin(angle))) + for i in range(ring_segs): + var angle = deg2rad(first_column_angle_deg + (i * ring_seg_angle)) + RING_LINE_SEGMENTS_VECTORS.push_back(Vector2(cos(angle), sin(angle))) + + for i in range(ring_segs/4): + var alpha := 1.0 - (i/float(ring_segs/4)) + ring_line_segments_alphas.push_back(alpha) + ring_line_segments_widths.push_back(lerp(alpha, 1.0, 0.5)) + + +func _draw(): + var mesh := ArrayMesh.new() + var dots := PoolVector2Array() + var dots_dict := {} + + var noteline_data : Image = noteline_array_image.get_rect(Rect2(0, 0, 16, 16)) + noteline_data.lock() + var i := 0 + var j := 0 + + for note in active_notes: + var position : float = (t+note_forecast_beats-note.time_hit)/note_forecast_beats + var note_center := screen_center + (radial_unit_vectors[note.column] * position * receptor_ring_radius) +# dots.push_back(note_center) +# if not dots_dict.has(position): +# dots_dict[position] = [] +# dots_dict[position].push_back(note.column) + noteline_data.set_pixel(i%16, i/16, Color(position, note.column, radial_col_angles[note.column])) + i += 1 + match note.type: + Note.NOTE_TAP: + var color = COLOR_ARRAY_DOUBLE_4 if note.double_hit else COLOR_ARRAY_TAP + make_tap_note(mesh, note.column, position, 1, color) + Note.NOTE_HOLD: + var color = COLOR_ARRAY_DOUBLE_8 if note.double_hit else COLOR_ARRAY_HOLD + var position_rel : float = (t+note_forecast_beats-note.time_release)/note_forecast_beats + if position_rel > 0: + var note_rel_center := screen_center + (radial_unit_vectors[note.column] * position_rel * receptor_ring_radius) +# dots.push_back(note_rel_center) + noteline_data.set_pixel(j%16, 15, Color(position_rel, note.column, radial_col_angles[note.column])) + j += 1 + make_hold_note(mesh, note.column, position, position_rel, 1.0, COLOR_ARRAY_HOLD_HELD) + Note.NOTE_SLIDE: + var color = COLOR_ARRAY_DOUBLE_4 if note.double_hit else COLOR_ARRAY_STAR + make_slide_note(mesh, note.column, position, 1.0, color) + +# var dot_scale := 1.0 - abs(0.25-fmod(t+0.25, 0.5)) +# var dot_inner := 6.0 * dot_scale +# var dot_outer := 9.0 * dot_scale + +# for dot in dots: +# draw_circle(dot, dot_inner, Color(1.0, 1.0, 1.0, 0.60)) +# draw_circle(dot, dot_outer, Color(1.0, 1.0, 1.0, 0.20)) + +# var line_inner := 3.0 * dot_scale +# var line_outer := 6.0 * dot_scale + noteline_data.unlock() + var noteline_data_tex = ImageTexture.new() + noteline_data_tex.create_from_image(noteline_data, 0) + $notelines.set_texture(noteline_data_tex) + +# for position in dots_dict: +# for col in dots_dict[position]: +# var c0 = col * RING_LINE_SEGMENTS_PER_COLUMN +# for i in range(ring_segs/4): +# var alpha :float = ring_line_segments_alphas[i]*dot_scale +# var width_scale : float = ring_line_segments_widths[i] +# draw_line(screen_center + RING_LINE_SEGMENTS_VECTORS[(c0+i)%ring_segs]*position*receptor_ring_radius, +# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0+i+1)%ring_segs]*position*receptor_ring_radius, +# Color(1.0, 1.0, 0.65, alpha*0.8), line_inner*width_scale) +# draw_line(screen_center + RING_LINE_SEGMENTS_VECTORS[(c0+i)%ring_segs]*position*receptor_ring_radius, +# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0+i+1)%ring_segs]*position*receptor_ring_radius, +# Color(1.0, 1.0, 0.65, alpha*0.2), line_outer*width_scale) +# draw_line(screen_center + RING_LINE_SEGMENTS_VECTORS[(c0-i)%ring_segs]*position*receptor_ring_radius, +# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0-i-1)%ring_segs]*position*receptor_ring_radius, +# Color(1.0, 1.0, 0.65, alpha*0.8), line_inner*width_scale) +# draw_line(screen_center + RING_LINE_SEGMENTS_VECTORS[(c0-i)%ring_segs]*position*receptor_ring_radius, +# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0-i-1)%ring_segs]*position*receptor_ring_radius, +# Color(1.0, 1.0, 0.65, alpha*0.2), line_outer*width_scale) + +# var alpha_array = PoolRealArray() +# alpha_array.resize(ring_segs) +# for i in range(ring_segs): +# alpha_array[i] = 0.0 +# for col in dots_dict[position]: +# var origin : int = col*RING_LINE_SEGMENTS_PER_COLUMN +# var affected_segs := ring_segs/4 +# alpha_array[origin] = 1.0 +# for i in range(affected_segs): +# alpha_array[(origin+i)%ring_segs] += 1.0 - i/float(affected_segs) +# alpha_array[(origin-i)%ring_segs] += 1.0 - i/float(affected_segs) +# for i in range(ring_segs): +# var alpha := min(alpha_array[i], 1.0)*dot_scale +# var width_scale : float = lerp(min(alpha_array[i], 1.0), 1.0, 0.5) +# draw_line(screen_center + RING_LINE_SEGMENTS_VECTORS[i]*position*receptor_ring_radius, +# screen_center + RING_LINE_SEGMENTS_VECTORS[(i+1)%ring_segs]*position*receptor_ring_radius, +# Color(1.0, 1.0, 0.65, alpha*0.8), line_inner*width_scale) +# draw_line(screen_center + RING_LINE_SEGMENTS_VECTORS[i]*position*receptor_ring_radius, +# screen_center + RING_LINE_SEGMENTS_VECTORS[(i+1)%ring_segs]*position*receptor_ring_radius, +# Color(1.0, 1.0, 0.65, alpha*0.2), line_outer*width_scale) + + $meshinstance.set_mesh(mesh) +# draw_mesh(mesh, tex) + +var noteline_array_image := Image.new() + +# Called when the node enters the scene tree for the first time. +func _ready(): + t = 0.0 + time = -2.0 + bpm = 120.0 + active_notes = [] + all_notes = [] + next_note_to_load = 0 + + $meshinstance.material.set_shader_param("star_color", COLOR_STAR) + $meshinstance.material.set_shader_param("held_color", COLOR_HOLD_HELD) + $meshinstance.material.set_shader_param("bps", bpm/60.0) + $meshinstance.material.set_shader_param("screen_size", get_viewport().get_size()) + $meshinstance.set_texture(tex) + + var rec_scale1 = (float(screen_height)/float(receptor_ring_radius))*0.5 + var uv_array_playfield := PoolVector2Array([Vector2(-1.0, -1.0)*rec_scale1, Vector2(-1.0, 1.0)*rec_scale1, Vector2(1.0, -1.0)*rec_scale1, Vector2(1.0, 1.0)*rec_scale1]) + var vertex_array_playfield := PoolVector2Array([ + Vector2(x_margin, screen_height), Vector2(x_margin, 0.0), + Vector2(x_margin+screen_height, screen_height), Vector2(x_margin+screen_height, 0.0)]) + var mesh_playfield := ArrayMesh.new() + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertex_array_playfield + arrays[Mesh.ARRAY_NORMAL] = NORMAL_ARRAY_4 + arrays[Mesh.ARRAY_TEX_UV] = uv_array_playfield + mesh_playfield.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays) + $notelines.set_mesh(mesh_playfield) + $notelines.material.set_shader_param("bps", bpm/60.0) + + noteline_array_image.create(16, 16, false, Image.FORMAT_RGBF) + 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") + bpm = 175.0 +# for bar in range(8): +# all_notes.push_back(Note.make_hold(bar*4, 1, bar%8)) +# for i in range(1, 8): +# all_notes.push_back(Note.make_tap(bar*4 + (i/2.0), (bar + i)%8)) +# all_notes.push_back(Note.make_tap(bar*4 + (7/2.0), (bar + 3)%8)) +# for bar in range(8, 16): +# all_notes.push_back(Note.make_hold(bar*4, 2, bar%8)) +# for i in range(1, 8): +# all_notes.push_back(Note.make_tap(bar*4 + (i/2.0), (bar + i)%8)) +# all_notes.push_back(Note.make_tap(bar*4 + ((i+0.5)/2.0), (bar + i)%8)) +# all_notes.push_back(Note.make_slide(bar*4 + ((i+1)/2.0), 1, (bar + i)%8, 0)) +# for bar in range(16, 24): +# all_notes.push_back(Note.make_hold(bar*4, 2, bar%8)) +# all_notes.push_back(Note.make_hold(bar*4, 1, (bar+1)%8)) +# for i in range(2, 8): +# all_notes.push_back(Note.make_tap(bar*4 + (i/2.0), (bar + i)%8)) +# all_notes.push_back(Note.make_hold(bar*4 + ((i+1)/2.0), 0.5, (bar + i)%8)) +# for bar in range(24, 32): +# all_notes.push_back(Note.make_hold(bar*4, 1, bar%8)) +# for i in range(1, 32): +# all_notes.push_back(Note.make_tap(bar*4 + (i/8.0), (bar + i)%8)) +# if (i%2) > 0: +# all_notes.push_back(Note.make_tap(bar*4 + (i/8.0), (bar + i + 4)%8)) +# for bar in range(32, 48): +# all_notes.push_back(Note.make_hold(bar*4, 1, bar%8)) +# for i in range(1, 32): +# all_notes.push_back(Note.make_tap(bar*4 + (i/8.0), (bar + i)%8)) +# all_notes.push_back(Note.make_tap(bar*4 + (i/8.0), (bar + i + 3)%8)) + + Note.process_doubles(all_notes) + +func game_time(realtime: float) -> float: + return time * bpm / 60.0 + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + $meshinstance.material.set_shader_param("bps", bpm/60.0) + $meshinstance.material.set_shader_param("screen_size", get_viewport().get_size()) + $notelines.material.set_shader_param("bps", bpm/60.0) + + var t_old := game_time(time) + time += delta + t = game_time(time) + if (t >= 0) and (t_old < 0): + get_node("/root/main/video").play() + + # Clean out expired notes + for i in range(len(active_notes)-1, -1, -1): + if active_notes[i].time_death < t: + active_notes.remove(i) + + # Add new notes as necessary + while true: + if next_note_to_load >= len(all_notes): + # All notes have been loaded, maybe do something + break + if all_notes[next_note_to_load].time_hit > (t + note_forecast_beats): + # Next chronological note isn't ready to load yet + break + # Next chronological note is ready to load, load it + active_notes.push_back(all_notes[next_note_to_load]) + next_note_to_load += 1 + + # DEBUG: Reset after all notes are done + if (len(active_notes) < 1) and (next_note_to_load >= len(all_notes)) and (time > 10.0): + time = fmod(time, 1.0) - 2.0 + next_note_to_load = 0 +# get_node("/root/main/video").set_stream_position(0.0) +# get_node("/root/main/video").play() + + # Redraw + $meshinstance.material.set_shader_param("screen_size", get_viewport().get_size()) + update() diff --git a/Receptors.gd b/Receptors.gd new file mode 100644 index 0000000..50e16e7 --- /dev/null +++ b/Receptors.gd @@ -0,0 +1,24 @@ +extends "res://main.gd" + +var ring_px := 4 +var receptor_px := 24 +var shadow_px := 5 +var shadow_color := Color.black +var receptor_color := Color.blue + +func _draw(): + # Receptor ring + var receptor_circle := arc_point_list(screen_center, receptor_ring_radius, 0.0, 360.0, 360) + var receptor_centers := arc_point_list(screen_center, receptor_ring_radius, 22.5, 360.0-22.5, 7) + + # Shadows + for i in range(len(receptor_circle)-1): + draw_line(receptor_circle[i], receptor_circle[i+1], shadow_color, ring_px + shadow_px, true) + for i in range(len(receptor_centers)): + draw_circle(receptor_centers[i], (receptor_px + shadow_px)/2, shadow_color) + + # Foregrounds + for i in range(len(receptor_circle)-1): + draw_line(receptor_circle[i], receptor_circle[i+1], receptor_color, ring_px, true) + for i in range(len(receptor_centers)): + draw_circle(receptor_centers[i], receptor_px/2, receptor_color) diff --git a/default_env.tres b/default_env.tres new file mode 100644 index 0000000..20207a4 --- /dev/null +++ b/default_env.tres @@ -0,0 +1,7 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +[resource] +background_mode = 2 +background_sky = SubResource( 1 ) diff --git a/lbl_main.gd b/lbl_main.gd new file mode 100644 index 0000000..8628b9c --- /dev/null +++ b/lbl_main.gd @@ -0,0 +1,50 @@ +extends Label + +var touch_points = {} # array containing all points touched on the screen +var fingers = 0 setget set_fingers # setter for show fingers number on screen +var txt_ball = preload("res://assets/ball.png") # preload our ball texture +var default_font = preload("res://assets/NotoSans.tres") # point to godot standard font + +func _ready(): + set_process_unhandled_input(true) # process user input + +########################################################################## +# draw fingers points on screen +func _draw(): + var touch_positions = PoolVector2Array() + # draw points + for i in touch_points: + var point = touch_points[i] + if point.pressed: + touch_positions.push_back(point.position) + # DRAW POINTS ################################################ + draw_texture(txt_ball, point.position - Vector2(24, 24)) + draw_string(default_font, point.position - Vector2(24, 24), str(i)) + if len(touch_positions) > 1: + for i in range(len(touch_positions)-1): + # Draw line + draw_line(touch_positions[i], touch_positions[i+1], Color(1,1,1,1)) + set_fingers(len(touch_positions)) +########################################################################## +func _input(event): + if (event is InputEventScreenDrag): + touch_points[event.index] = {pressed = true, position = event.position} + if (event is InputEventScreenTouch): + if event.pressed: + if not touch_points.has(event.index): + touch_points[event.index] = {} + touch_points[event.index].position = event.position # update position + touch_points[event.index].pressed = event.pressed # update "pressed" flag + else: + if touch_points.has(event.index): + touch_points.erase(event.index) + update() + +########################################################################## +# write how many fingers are tapping the screen +func set_fingers(value): + fingers = value + if fingers > 0: + set_text(str(fingers)) + else: + set_text("0") \ No newline at end of file diff --git a/main.gd b/main.gd new file mode 100644 index 0000000..924ed71 --- /dev/null +++ b/main.gd @@ -0,0 +1,27 @@ +extends Node2D + +# Declare member variables here. Examples: +var screen_height := 1080 +var x_margin := (1920 - screen_height)/2 +var screen_center := Vector2(1920/2, screen_height/2) + +var receptor_ring_radius := 460 + +func arc_point_list(center: Vector2, radius: float, angle_from:=0.0, angle_to:=360.0, points:=90) -> PoolVector2Array: + var point_list = PoolVector2Array() + for i in range(points + 1): + # positive Y going down makes for confusing angle plane. I prefer to work CCW as in mathematics. + var angle = -deg2rad(angle_from + i * (angle_to - angle_from) / points) + point_list.push_back(center + Vector2(cos(angle), sin(angle)) * radius) + return point_list + +# Called when the node enters the scene tree for the first time. +#func _ready(): +# pass # Replace with function body. + +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta): +# pass + + + diff --git a/main.tscn b/main.tscn new file mode 100644 index 0000000..7cedffa --- /dev/null +++ b/main.tscn @@ -0,0 +1,70 @@ +[gd_scene load_steps=12 format=2] + +[ext_resource path="res://main.gd" type="Script" id=1] +[ext_resource path="res://songs/cirno_9th_anniversary_best_cpu1_3M.webm" type="VideoStream" id=2] +[ext_resource path="res://Receptors.gd" type="Script" id=3] +[ext_resource path="res://NoteHandler.gd" type="Script" id=4] +[ext_resource path="res://shaders/notelines.shader" type="Shader" id=5] +[ext_resource path="res://shaders/notemesh.shader" type="Shader" id=6] +[ext_resource path="res://Bezel.gd" type="Script" id=7] +[ext_resource path="res://assets/NotoSans.tres" type="DynamicFont" id=8] +[ext_resource path="res://lbl_main.gd" type="Script" id=9] + +[sub_resource type="ShaderMaterial" id=1] +shader = ExtResource( 5 ) +shader_param/line_color = Plane( 0.8, 0.8, 1, 0.8 ) +shader_param/line_color_double = Plane( 1, 1, 0.6, 0.9 ) +shader_param/dot_color = Plane( 1, 1, 1, 0.8 ) +shader_param/bps = 1.0 +shader_param/line_thickness = 0.012 +shader_param/line_thickness_min = 0.0 +shader_param/dot_thickness = 0.033 +shader_param/dot_fullbright_thickness = 0.013 +shader_param/max_angle = 1.0708 + +[sub_resource type="ShaderMaterial" id=2] +shader = ExtResource( 6 ) +shader_param/bps = null +shader_param/star_color = null +shader_param/held_color = null +shader_param/screen_size = null + +[node name="main" type="Node2D"] +script = ExtResource( 1 ) + +[node name="video" type="VideoPlayer" parent="."] +anchor_right = 0.37 +margin_left = 420.0 +margin_right = 1500.0 +margin_bottom = 1080.0 +grow_horizontal = 2 +grow_vertical = 2 +rect_pivot_offset = Vector2( 540, 540 ) +mouse_filter = 2 +stream = ExtResource( 2 ) +volume_db = -11.6 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Receptors" type="Node2D" parent="."] +script = ExtResource( 3 ) + +[node name="NoteHandler" type="Node2D" parent="."] +script = ExtResource( 4 ) + +[node name="notelines" type="MeshInstance2D" parent="NoteHandler"] +material = SubResource( 1 ) + +[node name="meshinstance" type="MeshInstance2D" parent="NoteHandler"] +material = SubResource( 2 ) + +[node name="Bezel" type="Node2D" parent="."] +script = ExtResource( 7 ) + +[node name="lbl_main" type="Label" parent="."] +margin_right = 424.0 +margin_bottom = 216.0 +custom_fonts/font = ExtResource( 8 ) +text = "Fingers on the screen:" +script = ExtResource( 9 ) diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..99d0bf3 --- /dev/null +++ b/project.godot @@ -0,0 +1,57 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +_global_script_classes=[ ] +_global_script_class_icons={ + +} + +[application] + +config/name="Rhythm" +run/main_scene="res://main.tscn" +config/icon="res://icon.png" + +[audio] + +output_latency=5 +channel_disable_threshold_db=-100.0 +channel_disable_time=5.0 + +[autoload] + +Note="*res://Note.gd" +FileLoader="*res://FileLoader.gd" + +[debug] + +gdscript/warnings/unused_variable=false +gdscript/warnings/unused_class_variable=false +gdscript/warnings/unused_argument=false +gdscript/warnings/unused_signal=false +gdscript/warnings/return_value_discarded=false +gdscript/warnings/integer_division=false + +[display] + +window/size/width=1920 +window/size/height=1080 +window/handheld/orientation="sensor" +window/stretch/mode="2d" +window/stretch/aspect="keep_height" + +[gdnative] + +singletons=[ "res://addons/videodecoder.gdnlib" ] + +[rendering] + +quality/filters/msaa=1 +environment/default_environment="res://default_env.tres" diff --git a/shaders/notelines.shader b/shaders/notelines.shader new file mode 100644 index 0000000..45565f8 --- /dev/null +++ b/shaders/notelines.shader @@ -0,0 +1,134 @@ +shader_type canvas_item; +render_mode blend_premul_alpha; + +uniform vec4 line_color = vec4(0.8, 0.8, 1.0, 0.8); +uniform vec4 line_color_double = vec4(1.0, 1.0, 0.6, 0.9); +uniform vec4 dot_color = vec4(1.0, 1.0, 1.0, 0.8); +uniform float bps = 1.0; +uniform float line_thickness = 0.012; +uniform float line_thickness_min = 0.0; +uniform float dot_thickness = 0.033; +uniform float dot_fullbright_thickness = 0.013; +uniform float max_angle = 1.0708; //3.14159*0.5; //radians(90.0); + +//void vertex() { +//} + +float angle_diff(float a, float b) { + float d = mod((a - b), 6.28318); + if (d > 3.14159) d = 6.28318 - d; + return d; +} + +//vec4 blend_over(vec4 a, vec4 b) { +// // Blend a over b, preserving transparency +// vec4 color; +//// color.rgb = (a.rgb*a.a + b.rgb*b.a*(1.0-a.a))/(a.a + b.a*(1.0-a.a)); +//// color.a = min(a.a + b.a*(1.0-a.a), 1.0); +// color.a = min(mix(a.a, 1.0, b.a), 1.0); +// color.rgb = (a.rgb*a.a + b.rgb*b.a*(1.0-a.a))/color.a; +// return color; +//} +//vec4 blend_additive(vec4 a, vec4 b) { +// // Blend a over b, preserving transparency +// vec4 color; +// color.a = min(mix(a.a, 1.0, b.a), 1.0); +// color.rgb = mix(a.rgb, vec3(1.0), b.rgb*b.a*(1.0-a.a)); +// return color; +//} + + +void fragment() { + float dist = distance(UV, vec2(0.0)); + float angle = atan(-UV.y, UV.x); + float line_alpha = 0.0; + float line_double_alpha = 0.0; + float dot_alpha = 0.0; + + // Iterate over all the notes and check distance to them + bool last_double = false; + for (int i=0; i<238; i++){ + // x, y, z = distance, column, column_radians + vec3 sample = texelFetch(TEXTURE, ivec2(i%16, i/16), 0).xyz; + if (sample == vec3(0.0)) break; + float diff = abs(dist - sample.x); + // Short-circuit out if our radial difference is too high to matter in any case. + // We need the diff value calculated anyway so shouldn't add any overhead + // Assume dot_thickness is thickest uniform + if (diff > dot_thickness) continue; + + // Check for dot distance + vec2 uv = sample.x * vec2(cos(sample.z), -sin(sample.z)); + float dist2 = distance(UV, uv); + if (dist2 < dot_thickness){ + //dot_alpha += (dot_thickness-dist2)/dot_thickness; + float w = dot_thickness - dot_fullbright_thickness; + dot_alpha += (w-max(dist2-dot_fullbright_thickness, 0.0))/w; + } + + if (last_double){ // Already processed lines in last sample + last_double = false; + continue; + } + + float diff_a = angle_diff(angle, sample.z); + // Check if this note is a double with the next one + vec3 sample2 = texelFetch(TEXTURE, ivec2((i+1)%16, (i+1)/16), 0).xyz; + if (sample.x == sample2.x){ + // This note is a double! + last_double = true; + // Check for special case: directly opposite columns - full-thickness line 360° + if (sample.y == mod(sample2.y+4.0, 8.0)){ + if (diff < line_thickness){ + line_double_alpha += (line_thickness-diff)/line_thickness; + } + } else { + // Find the smallest arc between them, make it fully thick + float diff_a2 = angle_diff(angle, sample2.z); +// if ((diff_a<1.5708) && (diff_a2<1.5708)){ + if ((diff_a+diff_a2-0.0001) <= angle_diff(sample.z, sample2.z)){ + if (diff < line_thickness){ + line_double_alpha += (line_thickness-diff)/line_thickness; + } + } else { + // Fringe + float diff_amin = min(diff_a, diff_a2); + if (diff_amin < max_angle){ + float thickness = mix(line_thickness, line_thickness_min, diff_amin/max_angle); + if (diff < thickness){ + line_double_alpha += (thickness-diff)/line_thickness; + } + } + } + } + } else { + if (diff_a < max_angle){ + float thickness = mix(line_thickness, line_thickness_min, diff_a/max_angle); + if (diff < thickness){ + line_alpha += (thickness-diff)/line_thickness; + } + } + } + } + + // Draw release dots + for (int i=0; i<15; i++){ + vec3 sample = texelFetch(TEXTURE, ivec2(i, 15), 0).xyz; + if (sample == vec3(0.0)) break; + vec2 uv = sample.x * vec2(cos(sample.z), -sin(sample.z)); + float dist2 = distance(UV, uv); + if (dist2 < dot_thickness){ + //dot_alpha += (dot_thickness-dist2)/dot_thickness; + float w = dot_thickness - dot_fullbright_thickness; + dot_alpha += (w-max(dist2-dot_fullbright_thickness, 0.0))/w; + } + } + + line_alpha = min(line_alpha, 1.0) * line_color.a; + line_double_alpha = min(line_double_alpha, 1.0) * line_color_double.a; + dot_alpha = min(dot_alpha, 1.0) * dot_color.a; + COLOR.rgb = (line_color_double.rgb*line_double_alpha) + (line_color.rgb*line_alpha) + (dot_color.rgb*dot_alpha); + COLOR.a = 0.0; +// COLOR.rgb = (line_color_double.rgb*line_double_alpha + line_color.rgb*line_alpha*(1.0-line_double_alpha))/(line_double_alpha + line_alpha*(1.0-line_double_alpha)); +// COLOR.a = min(line_double_alpha + line_alpha*(1.0-line_double_alpha), 1.0); +} \ No newline at end of file diff --git a/shaders/notemesh.shader b/shaders/notemesh.shader new file mode 100644 index 0000000..1cc9f41 --- /dev/null +++ b/shaders/notemesh.shader @@ -0,0 +1,40 @@ +shader_type canvas_item; +//render_mode unshaded; + +uniform float bps; +uniform vec4 star_color : hint_color; +uniform vec4 held_color : hint_color; +uniform vec2 screen_size; + +//void vertex() { +//} + +void fragment() { + vec4 sample = texture(TEXTURE, UV); + float scale = sample.r; + float dist = distance(FRAGCOORD.xy, screen_size/2.0); + float dist_norm = dist*1.8 / screen_size.y; + if (COLOR.rgb == star_color.rgb){ + // Star ripple + COLOR.rg += dist_norm*0.33; + COLOR.rgb *= mix(abs(0.5-mod(TIME*bps*2.0+dist_norm, 1.0)), 1.0, 0.75); + COLOR.rgb *= scale; // Preserve black outlines + } else if (COLOR.rgb == held_color.rgb){ + // Hold note being held, flashy effects + COLOR.b *= mix(2.0*abs(0.5-mod(TIME*bps*2.0+dist_norm, 1.0)), 1.0, 0.35); + COLOR.g *= mix(1.0 - 2.0*abs(0.5-mod(TIME*bps*0.5+dist_norm, 1.0)), 0.0, 0.35); + COLOR.r *= mix(abs(0.5-mod(TIME*bps, 1.0)), 1.0, 0.85); + if (scale < 0.1){ // Make outlines WHITE + COLOR.rgb = vec3(mix(dist_norm, abs(0.5-mod(TIME*bps*8.0, 1.0)), 0.33)); + } + } else { + COLOR.gb += 0.1; + COLOR.rgb *= mix(abs(0.5-mod(TIME*bps, 1.0)), 1.0, 0.85); + COLOR.rgb *= scale; // Preserve black outlines + } + + COLOR.a = texture(TEXTURE, UV).a; + if (sample.rgb == vec3(1.0)){ + COLOR.rgb = vec3(1.0); + } +} \ No newline at end of file