Slide trails now draw correctly.

Still need to add sliding star.
This commit is contained in:
Luke Hubmayer-Werner 2019-11-14 19:12:49 +10:30
parent 1d6aa6472f
commit c7ee54458c
4 changed files with 82 additions and 94 deletions

17
Note.gd
View File

@ -19,7 +19,7 @@ static func make_hold(time_hit: float, duration: float, column: int) -> Dictiona
static func make_slide(time_hit: float, duration: float, column: int, column_release: int) -> Dictionary: static func make_slide(time_hit: float, duration: float, column: int, column_release: int) -> Dictionary:
var time_release := time_hit + duration var time_release := time_hit + duration
return {type=NOTE_SLIDE, time_hit=time_hit, time_release=time_release, 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} 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: static func make_touch(time_hit: float, location: Vector2) -> Dictionary:
@ -29,10 +29,19 @@ static func make_touch_hold(time_hit: float, duration: float, location: Vector2)
var time_release := time_hit + duration 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} 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): static func process_note_list(note_array: Array):
# Preprocess double hits # Preprocess double hits, assign Slide IDs
# If this were performance-critical, we'd single iterate it
# It's not though, so we lay it out simply
var slide_id := 0
if len(note_array): if len(note_array):
for i in range(len(note_array)-1): # Doubles
for i in len(note_array)-1:
if note_array[i].time_hit == note_array[i+1].time_hit: if note_array[i].time_hit == note_array[i+1].time_hit:
note_array[i].double_hit = true note_array[i].double_hit = true
note_array[i+1].double_hit = true note_array[i+1].double_hit = true
# Slides
for i in len(note_array):
if note_array[i].type == NOTE_SLIDE:
note_array[i].slide_id = slide_id
slide_id += 1

View File

@ -40,11 +40,13 @@ const DEG135 := deg2rad(135.0)
var time := 0.0 var time := 0.0
var t := 0.0 var t := 0.0
var bpm := 120.0 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_notes := []
var all_notes := [] var all_notes := []
var next_note_to_load := 0 var next_note_to_load := 0
var slide_trail_meshes := [] var slide_trail_meshes := {}
var slide_trail_mesh_instances := [] var slide_trail_mesh_instances := {}
# UV vertex arrays for our sprites # UV vertex arrays for our sprites
# tap/star/arrow are 4-vertex 2-triangle simple squares # tap/star/arrow are 4-vertex 2-triangle simple squares
@ -68,6 +70,7 @@ var NORMAL_ARRAY_8 := PoolVector3Array([
DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL, DEFAULT_NORMAL
]) ])
# ----------------------------------------------------------------------------------------------------------------------------------------------------
# Helper functions to generate meshes from vertex arrays # Helper functions to generate meshes from vertex arrays
func make_tap_mesh(mesh: ArrayMesh, vertex_array, color_array = theme.COLOR_ARRAY_TAP): func make_tap_mesh(mesh: ArrayMesh, vertex_array, color_array = theme.COLOR_ARRAY_TAP):
var arrays = [] var arrays = []
@ -116,12 +119,11 @@ func make_slide_trail_mesh(note: Dictionary) -> ArrayMesh:
var uvs := PoolVector2Array() var uvs := PoolVector2Array()
var colors := PoolColorArray() var colors := PoolColorArray()
var size := theme.sprite_size2 var size := theme.sprite_size2
var color := Color(0.67, 0.67, 1.0)
if note.double_hit:
color = Color(1.0, 1.0, 0.35)
# First we need to determine how many arrows to leave. # First we need to determine how many arrows to leave.
# Chord length is 2r*sin(theta/2)
# Arc length is r*theta (in rads)
# 18 for a 3chord in maimai
# 20 for a 4chord
# 6 per arc segment +1 for every receptor crossed over, ~7 per
var unit_length: float var unit_length: float
var trail_length: int var trail_length: int
match note.slide_type: match note.slide_type:
@ -139,14 +141,12 @@ func make_slide_trail_mesh(note: Dictionary) -> ArrayMesh:
uvs.append_array(UV_ARRAY_SLIDE_ARROW if i%3 else UV_ARRAY_SLIDE_ARROW2) uvs.append_array(UV_ARRAY_SLIDE_ARROW if i%3 else UV_ARRAY_SLIDE_ARROW2)
for j in 3: for j in 3:
# uvs[i*3+j] = UV_ARRAY_SLIDE_ARROW[j] if i%2 else UV_ARRAY_SLIDE_ARROW2[j] # uvs[i*3+j] = UV_ARRAY_SLIDE_ARROW[j] if i%2 else UV_ARRAY_SLIDE_ARROW2[j]
colors[i*3+j] = Color(0.67, 0.67, 1.0, 1.0+float(i)) colors[i*3+j] = Color(color.r, color.g, color.b, (1.0+float(i))/float(trail_length))
match note.slide_type: match note.slide_type:
Note.SlideType.CHORD: Note.SlideType.CHORD:
# var angle : float = RADIAL_UNIT_VECTORS[note.column].angle_to_point(RADIAL_UNIT_VECTORS[note.column_release])
var start : Vector2 = RADIAL_UNIT_VECTORS[note.column] * theme.receptor_ring_radius var start : Vector2 = RADIAL_UNIT_VECTORS[note.column] * theme.receptor_ring_radius
var end : Vector2 = RADIAL_UNIT_VECTORS[note.column_release] * theme.receptor_ring_radius var end : Vector2 = RADIAL_UNIT_VECTORS[note.column_release] * theme.receptor_ring_radius
# var angle : float = start.angle_to_point(end) # seems to be out by 180° for no good reason
var angle : float = (end-start).angle() var angle : float = (end-start).angle()
var uv1o : Vector2 = polar2cartesian(size, angle) var uv1o : Vector2 = polar2cartesian(size, angle)
var uv2o : Vector2 = polar2cartesian(size, angle+PI/2.0) var uv2o : Vector2 = polar2cartesian(size, angle+PI/2.0)
@ -225,13 +225,13 @@ func make_hold_note(mesh: ArrayMesh, column: int, position1: float, position2: f
make_hold_mesh(mesh, vertices, color_array) make_hold_mesh(mesh, vertices, color_array)
return mesh return mesh
func make_slide_note(mesh: ArrayMesh, column: int, position: float, scale := 1.0, color_array := theme.COLOR_ARRAY_STAR) -> ArrayMesh: func make_slide_note(mesh: ArrayMesh, column: int, position: float, speed := 1.0, scale := 1.0, color_array := theme.COLOR_ARRAY_STAR) -> ArrayMesh:
if position < theme.INNER_NOTE_CIRCLE_RATIO: if position < theme.INNER_NOTE_CIRCLE_RATIO:
scale *= position/theme.INNER_NOTE_CIRCLE_RATIO scale *= position/theme.INNER_NOTE_CIRCLE_RATIO
position = theme.INNER_NOTE_CIRCLE_RATIO position = theme.INNER_NOTE_CIRCLE_RATIO
var note_center = screen_center + (RADIAL_UNIT_VECTORS[column] * position * theme.receptor_ring_radius) var note_center = screen_center + (RADIAL_UNIT_VECTORS[column] * position * theme.receptor_ring_radius)
var dim = theme.sprite_size2 * scale * SQRT2 var dim = theme.sprite_size2 * scale * SQRT2
var angle = deg2rad(fmod(t*270.0, 360.0)) var angle = fmod(t*speed, 1.0)*TAU
var a1 = angle - DEG45 var a1 = angle - DEG45
var a2 = angle + DEG45 var a2 = angle + DEG45
var a3 = angle - DEG135 var a3 = angle - DEG135
@ -262,11 +262,6 @@ func _draw():
for note in active_notes: for note in active_notes:
var position : float = (t+theme.note_forecast_beats-note.time_hit)/theme.note_forecast_beats var position : float = (t+theme.note_forecast_beats-note.time_hit)/theme.note_forecast_beats
var note_center := screen_center + (RADIAL_UNIT_VECTORS[note.column] * position * theme.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])) noteline_data.set_pixel(i%16, i/16, Color(position, note.column, RADIAL_COL_ANGLES[note.column]))
i += 1 i += 1
match note.type: match note.type:
@ -278,70 +273,29 @@ func _draw():
var position_rel : float = (t+theme.note_forecast_beats-note.time_release)/theme.note_forecast_beats var position_rel : float = (t+theme.note_forecast_beats-note.time_release)/theme.note_forecast_beats
if position_rel > 0: if position_rel > 0:
var note_rel_center := screen_center + (RADIAL_UNIT_VECTORS[note.column] * position_rel * theme.receptor_ring_radius) var note_rel_center := screen_center + (RADIAL_UNIT_VECTORS[note.column] * position_rel * theme.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])) noteline_data.set_pixel(j%16, 15, Color(position_rel, note.column, RADIAL_COL_ANGLES[note.column]))
j += 1 j += 1
make_hold_note(mesh, note.column, position, position_rel, 1.0, theme.COLOR_ARRAY_HOLD_HELD) make_hold_note(mesh, note.column, position, position_rel, 1.0, theme.COLOR_ARRAY_HOLD_HELD)
Note.NOTE_SLIDE: Note.NOTE_SLIDE:
var color = theme.COLOR_ARRAY_DOUBLE_4 if note.double_hit else theme.COLOR_ARRAY_STAR var color = theme.COLOR_ARRAY_DOUBLE_4 if note.double_hit else theme.COLOR_ARRAY_STAR
make_slide_note(mesh, note.column, position, 1.0, color) make_slide_note(mesh, note.column, position, 1.0, color)
var trail_alpha := 1.0
if position < theme.INNER_NOTE_CIRCLE_RATIO:
trail_alpha = 0.0
elif position < 1.0:
trail_alpha = min(1.0, (position-theme.INNER_NOTE_CIRCLE_RATIO)/(1-theme.INNER_NOTE_CIRCLE_RATIO*2))
else:
var trail_progress : float = clamp((t - note.time_hit - theme.SLIDE_DELAY)/(note.duration - theme.SLIDE_DELAY), 0.0, 1.0)
slide_trail_mesh_instances[note.slide_id].material.set_shader_param("trail_progress", trail_progress)
if t > note.time_release:
trail_alpha = max(1 - (t - note.time_release)/Note.DEATH_DELAY, 0.0)
slide_trail_mesh_instances[note.slide_id].material.set_shader_param("base_alpha", trail_alpha*0.88)
noteline_data.unlock() noteline_data.unlock()
var noteline_data_tex = ImageTexture.new() var noteline_data_tex = ImageTexture.new()
noteline_data_tex.create_from_image(noteline_data, 0) noteline_data_tex.create_from_image(noteline_data, 0)
$notelines.set_texture(noteline_data_tex) $notelines.set_texture(noteline_data_tex)
# 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
# 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*theme.receptor_ring_radius,
# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0+i+1)%ring_segs]*position*theme.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*theme.receptor_ring_radius,
# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0+i+1)%ring_segs]*position*theme.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*theme.receptor_ring_radius,
# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0-i-1)%ring_segs]*position*theme.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*theme.receptor_ring_radius,
# screen_center + RING_LINE_SEGMENTS_VECTORS[(c0-i-1)%ring_segs]*position*theme.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*theme.receptor_ring_radius,
# screen_center + RING_LINE_SEGMENTS_VECTORS[(i+1)%ring_segs]*position*theme.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*theme.receptor_ring_radius,
# screen_center + RING_LINE_SEGMENTS_VECTORS[(i+1)%ring_segs]*position*theme.receptor_ring_radius,
# Color(1.0, 1.0, 0.65, alpha*0.2), line_outer*width_scale)
$meshinstance.set_mesh(mesh) $meshinstance.set_mesh(mesh)
# draw_mesh(mesh, tex) # draw_mesh(mesh, tex)
@ -382,17 +336,28 @@ func _ready():
# Format: first 15 rows are for hit events, last row is for releases only (no ring glow) # 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.Test.stress_pattern()
bpm = 175.0 bpm = 175.0
sync_offset_audio = 0.553
sync_offset_video = 0.553
# all_notes = FileLoader.Test.stress_pattern()
Note.process_doubles(all_notes) Note.process_note_list(all_notes)
for note in all_notes: for note in all_notes:
if note.type == Note.NOTE_SLIDE: if note.type == Note.NOTE_SLIDE:
slide_trail_meshes.push_back(make_slide_trail_mesh(note)) slide_trail_meshes[note.slide_id] = make_slide_trail_mesh(note)
func game_time(realtime: float) -> float: func game_time(realtime: float) -> float:
return time * bpm / 60.0 return time * bpm / 60.0
func video_start_time() -> float:
# We give a 4 beat delay until the first note of the chart. Should have a sound for each beat.
var four_beats := 4.0 * 60.0/bpm
return four_beats - sync_offset_video
func audio_start_time() -> float:
var four_beats := 4.0 * 60.0/bpm
return four_beats - sync_offset_audio
# Called every frame. 'delta' is the elapsed time since the previous frame. # Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta): func _process(delta):
$meshinstance.material.set_shader_param("bps", bpm/60.0) $meshinstance.material.set_shader_param("bps", bpm/60.0)
@ -401,12 +366,21 @@ func _process(delta):
var t_old := game_time(time) var t_old := game_time(time)
time += delta time += delta
t = game_time(time) t = game_time(time)
if (t_old < 0) and (t >= 0):
# if (t_old < 0) and (t >= 0):
# get_node("/root/main/video").play()
var vt_delta := time - video_start_time()
if (vt_delta >= 0.0) and not get_node("/root/main/video").is_playing():
get_node("/root/main/video").play() get_node("/root/main/video").play()
get_node("/root/main/video").set_stream_position(vt_delta)
# Clean out expired notes # Clean out expired notes
for i in range(len(active_notes)-1, -1, -1): for i in range(len(active_notes)-1, -1, -1):
if active_notes[i].time_death < t: var note = active_notes[i]
if note.time_death < t:
if note.type == Note.NOTE_SLIDE:
$SlideTrailHandler.remove_child(slide_trail_mesh_instances[note.slide_id])
slide_trail_mesh_instances.erase(note.slide_id)
active_notes.remove(i) active_notes.remove(i)
# Add new notes as necessary # Add new notes as necessary
@ -418,24 +392,24 @@ func _process(delta):
# Next chronological note isn't ready to load yet # Next chronological note isn't ready to load yet
break break
# Next chronological note is ready to load, load it # Next chronological note is ready to load, load it
active_notes.push_back(all_notes[next_note_to_load]) var note = all_notes[next_note_to_load]
if active_notes[-1].type == Note.NOTE_SLIDE: active_notes.push_back(note)
slide_trail_mesh_instances.push_back(MeshInstance2D.new()) if note.type == Note.NOTE_SLIDE:
slide_trail_mesh_instances[-1].set_mesh(slide_trail_meshes.pop_front()) var meshi = MeshInstance2D.new()
# slide_trail_mesh_instances[-1].set_position(screen_center) meshi.set_mesh(slide_trail_meshes[note.slide_id])
$SlideTrailHandler.add_child(slide_trail_mesh_instances[-1]) # meshi.set_position(screen_center)
slide_trail_mesh_instances[-1].set_material(slide_trail_shadermaterial) meshi.set_material(slide_trail_shadermaterial.duplicate())
slide_trail_mesh_instances[-1].material.set_shader_param("trail_progress", 0.0) meshi.material.set_shader_param("trail_progress", 0.0)
slide_trail_mesh_instances[-1].set_texture(tex_slide_arrow) meshi.set_texture(tex_slide_arrow)
slide_trail_mesh_instances[note.slide_id] = meshi
$SlideTrailHandler.add_child(meshi)
next_note_to_load += 1 next_note_to_load += 1
# DEBUG: Reset after all notes are done # DEBUG: Reset after all notes are done
if (len(active_notes) < 1) and (next_note_to_load >= len(all_notes)) and (time > 10.0): if (len(active_notes) < 1) and (next_note_to_load >= len(all_notes)) and (time > 10.0) and not get_node("/root/main/video").is_playing():
time = fmod(time, 1.0) - 2.0 time = -2.0
next_note_to_load = 0 next_note_to_load = 0
# get_node("/root/main/video").set_stream_position(0.0)
# get_node("/root/main/video").play()
# Redraw # Redraw
$meshinstance.material.set_shader_param("screen_size", get_viewport().get_size()) $meshinstance.material.set_shader_param("screen_size", get_viewport().get_size())

View File

@ -11,7 +11,11 @@ uniform float bps = 1.0;
// We don't need vertex alpha normally so we can just set that to large whole numbers // We don't need vertex alpha normally so we can just set that to large whole numbers
// on each arrow (1.0, 2.0, 3.0, ... 50.0) and then use a uniform progress float. // on each arrow (1.0, 2.0, 3.0, ... 50.0) and then use a uniform progress float.
void vertex() { void vertex() {
COLOR.a = clamp(COLOR.a-trail_progress, 0.0, 1.0); // COLOR.a = clamp(COLOR.a-trail_progress, 0.0, 1.0);
if (COLOR.a<trail_progress)
COLOR.a = 0.0;
else
COLOR.a = 1.0;
} }
void fragment() { void fragment() {

View File

@ -1,8 +1,9 @@
extends Node extends Node
var receptor_ring_radius := 460 var receptor_ring_radius := 460
var note_forecast_beats := 2.0 var note_forecast_beats := 2.0 # Notes start to appear this many beats before you need to tap them
const INNER_NOTE_CIRCLE_RATIO := 0.3 const INNER_NOTE_CIRCLE_RATIO := 0.3 # Notes under this far from the center will zoom into existence
const SLIDE_DELAY := 0.5 # Time in beats between the tap of the star and the start of the visual slide
var sprite_size := 128 var sprite_size := 128
var sprite_size2 := sprite_size/2 var sprite_size2 := sprite_size/2