├── assets ├── ui │ └── .gitkeep ├── levels │ └── .gitkeep └── game │ └── hit_effects │ ├── square.png │ └── square.png.import ├── exports └── .gitkeep ├── icon.png ├── .gitattributes ├── scripts ├── hit_sound.gd ├── hit_effects.gd ├── particle.gd ├── globals.gd ├── homepage.gd ├── note_part.gd ├── judgeline.gd └── level.gd ├── shaders ├── darkening.gdshader ├── h_blur.gdshader └── v_blur.gdshader ├── .gitignore ├── scenes ├── square.tscn ├── hit_sound.tscn ├── judgeline.tscn ├── homepage.tscn ├── note_part.tscn ├── level.tscn └── hit_effects.tscn ├── default_bus_layout.tres ├── icon.png.import ├── icon.svg ├── icon.svg.import ├── project.godot ├── README.md └── LICENSE /assets/ui/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exports/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/levels/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naptie/godot-phigros/HEAD/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /scripts/hit_sound.gd: -------------------------------------------------------------------------------- 1 | extends AudioStreamPlayer2D 2 | 3 | 4 | func _on_finished(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /scripts/hit_effects.gd: -------------------------------------------------------------------------------- 1 | extends AnimatedSprite2D 2 | 3 | 4 | func _on_animation_finished(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /assets/game/hit_effects/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naptie/godot-phigros/HEAD/assets/game/hit_effects/square.png -------------------------------------------------------------------------------- /shaders/darkening.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform float darkness : hint_range(0.0, 1.0) = 0.5; 4 | 5 | void fragment() { 6 | vec4 texture_color = texture(TEXTURE, UV); 7 | vec3 darkened_color = texture_color.rgb * (1.0 - darkness); 8 | COLOR = vec4(darkened_color, texture_color.a); 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | assets/game/* 5 | !assets/game/hit_effects 6 | assets/game/hit_effects/* 7 | !assets/game/hit_effects/square.png 8 | !assets/game/hit_effects/square.png.import 9 | assets/levels/* 10 | assets/ui/* 11 | 12 | exports/* 13 | 14 | export_presets.cfg 15 | 16 | !**/.gitkeep -------------------------------------------------------------------------------- /scenes/square.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://cgpvbhf2vmj25"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://pjh3kso1wvw5" path="res://assets/game/hit_effects/square.png" id="1_1lcs3"] 4 | 5 | [node name="Square" type="Sprite2D"] 6 | modulate = Color(1, 1, 1, 0.498039) 7 | scale = Vector2(40, 40) 8 | texture = ExtResource("1_1lcs3") 9 | -------------------------------------------------------------------------------- /scenes/hit_sound.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://c0re0xr7qm7ms"] 2 | 3 | [ext_resource type="Script" path="res://scripts/hit_sound.gd" id="1_15yef"] 4 | 5 | [node name="HitSound" type="AudioStreamPlayer2D"] 6 | bus = &"Effects" 7 | script = ExtResource("1_15yef") 8 | 9 | [connection signal="finished" from="." to="." method="_on_finished"] 10 | -------------------------------------------------------------------------------- /scripts/particle.gd: -------------------------------------------------------------------------------- 1 | extends Sprite2D 2 | 3 | 4 | func _ready(): 5 | var angle = randf_range(0, 2 * PI) 6 | var tween = create_tween().set_parallel(true).set_trans(Tween.TRANS_CUBIC) 7 | tween.tween_property(self, "position", Vector2(cos(angle), sin(angle)) * randf_range(100, 400), 0.5).set_ease(Tween.EASE_OUT) 8 | tween.tween_property(self, "self_modulate:a", 0, 0.5).set_ease(Tween.EASE_IN) 9 | tween.tween_property(self, "scale", Vector2.ONE * 30, 0.3).set_ease(Tween.EASE_OUT) 10 | tween.tween_property(self, "scale", Vector2.ZERO, 0.3).set_ease(Tween.EASE_IN).set_delay(0.3) 11 | 12 | -------------------------------------------------------------------------------- /shaders/h_blur.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform int kernel_size : hint_range(1, 160) = 5; 4 | uniform float sigma : hint_range(0.1, 50.0) = 2.0; 5 | 6 | float gaussian(int x, float sigmaa) { 7 | return exp(-float(x*x) / (2.0 * sigmaa * sigmaa)); 8 | } 9 | 10 | void fragment() { 11 | vec2 tex_size = 1.0 / TEXTURE_PIXEL_SIZE; 12 | vec2 uv = UV; 13 | vec3 color = vec3(0.0); 14 | float weight_sum = 0.0; 15 | 16 | for (int i = -kernel_size; i <= kernel_size; i++) { 17 | float weight = gaussian(i, sigma); 18 | vec2 offset = vec2(0.0, float(i)) / tex_size; 19 | color += texture(TEXTURE, uv + offset).rgb * weight; 20 | weight_sum += weight; 21 | } 22 | 23 | COLOR.rgb = color / weight_sum; 24 | } 25 | -------------------------------------------------------------------------------- /scenes/judgeline.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://dis1a70a2xbcq"] 2 | 3 | [ext_resource type="Script" path="res://scripts/judgeline.gd" id="1_hx8pw"] 4 | [ext_resource type="Texture2D" uid="uid://chxknvbxjx6sw" path="res://assets/game/line.png" id="2_eud2y"] 5 | 6 | [node name="Judgeline" type="Node2D"] 7 | z_index = -4 8 | z_as_relative = false 9 | script = ExtResource("1_hx8pw") 10 | 11 | [node name="Camera2D" type="Camera2D" parent="."] 12 | 13 | [node name="Texture" type="Sprite2D" parent="."] 14 | scale = Vector2(1.44, 1.44) 15 | texture = ExtResource("2_eud2y") 16 | 17 | [node name="Label" type="Label" parent="."] 18 | offset_left = -44.0 19 | offset_top = 6.0 20 | offset_right = 44.0 21 | offset_bottom = 75.0 22 | theme_override_font_sizes/font_size = 50 23 | horizontal_alignment = 1 24 | -------------------------------------------------------------------------------- /shaders/v_blur.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform int kernel_size : hint_range(1, 160) = 5; 4 | uniform float sigma : hint_range(0.1, 50.0) = 2.0; 5 | uniform float darkness : hint_range(0.0, 1.0) = 0.5; 6 | 7 | float gaussian(int x, float sigmaa) { 8 | return exp(-float(x*x) / (2.0 * sigmaa * sigmaa)); 9 | } 10 | 11 | void fragment() { 12 | vec2 tex_size = 1.0 / TEXTURE_PIXEL_SIZE; 13 | vec2 uv = UV; 14 | vec3 color = vec3(0.0); 15 | float weight_sum = 0.0; 16 | 17 | for (int i = -kernel_size; i <= kernel_size; i++) { 18 | float weight = gaussian(i, sigma); 19 | vec2 offset = vec2(float(i), 0.0) / tex_size; 20 | color += texture(TEXTURE, uv + offset).rgb * weight; 21 | weight_sum += weight; 22 | } 23 | 24 | COLOR.rgb = color * (1.0 - darkness) / weight_sum; 25 | } 26 | -------------------------------------------------------------------------------- /default_bus_layout.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AudioBusLayout" load_steps=3 format=3 uid="uid://thhayoglhxrd"] 2 | 3 | [sub_resource type="AudioEffectLimiter" id="AudioEffectLimiter_u22u8"] 4 | resource_name = "Limiter" 5 | 6 | [sub_resource type="AudioEffectLimiter" id="AudioEffectLimiter_1uigd"] 7 | resource_name = "Limiter" 8 | 9 | [resource] 10 | bus/0/effect/0/effect = SubResource("AudioEffectLimiter_u22u8") 11 | bus/0/effect/0/enabled = true 12 | bus/1/name = &"Music" 13 | bus/1/solo = false 14 | bus/1/mute = false 15 | bus/1/bypass_fx = false 16 | bus/1/volume_db = -2.51026 17 | bus/1/send = &"Master" 18 | bus/2/name = &"Effects" 19 | bus/2/solo = false 20 | bus/2/mute = false 21 | bus/2/bypass_fx = false 22 | bus/2/volume_db = 6.02 23 | bus/2/send = &"Master" 24 | bus/2/effect/0/effect = SubResource("AudioEffectLimiter_1uigd") 25 | bus/2/effect/0/enabled = true 26 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://xtp4xn7cc0g7" 6 | path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.png" 14 | dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/game/hit_effects/square.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://pjh3kso1wvw5" 6 | path="res://.godot/imported/square.png-0b5c98a24d102dff4613ac82c3e4deb7.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/game/hit_effects/square.png" 14 | dest_files=["res://.godot/imported/square.png-0b5c98a24d102dff4613ac82c3e4deb7.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dx8r1toscc3q5" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="godot-phigros" 14 | config/version="0.0.1" 15 | run/main_scene="res://scenes/homepage.tscn" 16 | config/features=PackedStringArray("4.2", "Mobile") 17 | config/icon="res://icon.png" 18 | 19 | [autoload] 20 | 21 | Globals="*res://scripts/globals.gd" 22 | 23 | [display] 24 | 25 | window/size/viewport_width=1920 26 | window/size/viewport_height=1080 27 | window/stretch/mode="canvas_items" 28 | window/stretch/aspect="expand" 29 | window/handheld/orientation=4 30 | window/vsync/vsync_mode=2 31 | 32 | [input_devices] 33 | 34 | pointing/emulate_touch_from_mouse=true 35 | 36 | [rendering] 37 | 38 | renderer/rendering_method="mobile" 39 | textures/vram_compression/import_etc2_astc=true 40 | anti_aliasing/quality/msaa_2d=1 41 | -------------------------------------------------------------------------------- /scripts/globals.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | const BASE_WIDTH := 1920.0 4 | const BASE_HEIGHT := 1080.0 5 | const EPS := 1e-4 6 | 7 | var note_size := 0.25 8 | var is_autoplay := true 9 | var bg_darkness := 0.5 10 | var judge_radius := 300 11 | var flick_velocity := 300 12 | var finger_lock := false 13 | var perfect := 80 14 | var good := 160 15 | var bad := 180 16 | 17 | 18 | func is_in_screen(position: Vector2): 19 | return is_in_area(position, Vector2(-BASE_WIDTH / 2, -BASE_HEIGHT / 2), Vector2(BASE_WIDTH / 2, BASE_HEIGHT / 2)) 20 | 21 | 22 | func is_in_area(position: Vector2, top_left: Vector2, bottom_right: Vector2): 23 | return position.x >= top_left.x - EPS and position.x <= bottom_right.x + EPS \ 24 | and position.y >= top_left.y - EPS and position.y <= bottom_right.y + EPS 25 | 26 | 27 | func ct(time_in_seconds: float, bpm: float) -> float: 28 | return time_in_seconds * bpm * 32 / 60 29 | 30 | 31 | func cs(chart_time: float, bpm: float) -> float: 32 | return chart_time * 60 / bpm / 32 33 | 34 | 35 | func is_in(value: float, min: float, max: float): 36 | return value >= min and value <= max 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [⚠️DEPRECATED⚠️] godot-phigros 2 | 3 | > [!WARNING] 4 | > **本项目已弃用。** 有关本人参与制作的新版 Phigros 模拟器,请见 **[PhiZone/player](https://github.com/PhiZone/player)** 项目。 5 | 6 | 使用 Godot 实现的 Phigros 模拟器。 7 | 8 | 系本人第一个 Godot 项目 / 游戏作品;本项目仅用于学习 Godot。 9 | 10 | ### 待办 11 | 12 | - 音符高度计算优化 13 | - Flick 判定优化 14 | - 结算界面 15 | 16 | ### 说明 17 | 18 | 请按以下格式放置资源文件: 19 | ``` 20 | └── assets 21 | ├── game 22 | │ ├── hit_effects 23 | │ │ ├── img-1.png 24 | │ │ ├── img-2.png 25 | │ │ ├── img-3.png 26 | │ │ ├── ... 27 | │ │ ├── img-30.png 28 | │ │ └── img-31.png 29 | │ ├── Drag.png 30 | │ ├── drag.wav 31 | │ ├── DragHL.png 32 | │ ├── Flick.png 33 | │ ├── flick.wav 34 | │ ├── FlickHL.png 35 | │ ├── Hold.png 36 | │ ├── HoldEnd.png 37 | │ ├── HoldHead.png 38 | │ ├── HoldHeadHL.png 39 | │ ├── HoldHL.png 40 | │ ├── line.png 41 | │ ├── Tap.png 42 | │ ├── tap.wav 43 | │ └── TapHL.png 44 | └── levels 45 | ├── {LevelName} 46 | │ ├── Chart_AT.json 47 | │ ├── Chart_EZ.json 48 | │ ├── Chart_HD.json 49 | │ ├── Chart_IN.json 50 | │ ├── Chart_SP.json 51 | │ ├── Illustration.png 52 | │ └── music.ogg 53 | ├── ... 54 | ``` 55 | -------------------------------------------------------------------------------- /scenes/homepage.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dufkfsontgdo4"] 2 | 3 | [ext_resource type="Script" path="res://scripts/homepage.gd" id="1_vrkwx"] 4 | 5 | [node name="Homepage" type="Node2D"] 6 | script = ExtResource("1_vrkwx") 7 | 8 | [node name="Button" type="Button" parent="."] 9 | offset_left = 297.0 10 | offset_top = 430.0 11 | offset_right = 949.0 12 | offset_bottom = 521.0 13 | theme_override_font_sizes/font_size = 60 14 | disabled = true 15 | text = "Play" 16 | metadata/_edit_use_anchors_ = true 17 | 18 | [node name="ItemList" type="ItemList" parent="."] 19 | offset_left = -946.0 20 | offset_top = -525.0 21 | offset_right = 272.0 22 | offset_bottom = 523.0 23 | metadata/_edit_use_anchors_ = true 24 | 25 | [node name="Camera2D" type="Camera2D" parent="."] 26 | 27 | [node name="ItemList2" type="ItemList" parent="."] 28 | offset_left = 296.0 29 | offset_top = -525.0 30 | offset_right = 949.0 31 | offset_bottom = 407.0 32 | item_count = 5 33 | item_0/text = "EZ" 34 | item_1/text = "HD" 35 | item_2/text = "IN" 36 | item_3/text = "AT" 37 | item_4/text = "SP" 38 | 39 | [node name="DifficultyNotFound" type="Label" parent="."] 40 | modulate = Color(1, 1, 1, 0) 41 | offset_left = -447.0 42 | offset_top = -35.0 43 | offset_right = 448.0 44 | offset_bottom = 35.0 45 | theme_override_font_sizes/font_size = 50 46 | text = "The selected difficulty does not exist!" 47 | 48 | [connection signal="pressed" from="Button" to="." method="_on_button_pressed"] 49 | [connection signal="empty_clicked" from="ItemList" to="." method="_on_item_list_empty_clicked"] 50 | [connection signal="item_selected" from="ItemList" to="." method="_on_item_list_item_selected"] 51 | -------------------------------------------------------------------------------- /scripts/homepage.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const LEVEL_ROOT := "res://assets/levels" 4 | 5 | @onready var item_list = $ItemList 6 | @onready var item_list_2 = $ItemList2 7 | @onready var button = $Button 8 | @onready var difficulty_not_found = $DifficultyNotFound 9 | 10 | var levels : Array[String] 11 | var difficulties := ["EZ", "HD", "IN", "AT", "SP"] 12 | 13 | func _ready(): 14 | #_on_size_changed() 15 | #get_tree().root.size_changed.connect(_on_size_changed) 16 | seek_levels() 17 | for level in levels: 18 | item_list.add_item(level) 19 | 20 | 21 | func seek_levels(): 22 | var dir = DirAccess.open(LEVEL_ROOT) 23 | dir.list_dir_begin() 24 | var file_name = dir.get_next() 25 | while file_name != "": 26 | if dir.current_is_dir(): 27 | levels.append(file_name) 28 | file_name = dir.get_next() 29 | dir.list_dir_end() 30 | 31 | 32 | func _on_button_pressed(): 33 | var path = LEVEL_ROOT + "/" + levels[item_list.get_selected_items()[0]] 34 | var difficulty = difficulties[item_list_2.get_selected_items()[0]] if item_list_2.get_selected_items().size() > 0 else "IN" 35 | if !FileAccess.file_exists(path + "/" + "Chart_" + difficulty + ".json"): 36 | difficulty_not_found.modulate.a = 1 37 | await get_tree().create_timer(2).timeout 38 | difficulty_not_found.modulate.a = 0 39 | return 40 | var level = load("res://scenes/level.tscn").instantiate() 41 | level.path = path 42 | level.level = difficulty 43 | queue_free() 44 | get_tree().root.add_child(level) 45 | 46 | 47 | func _on_item_list_item_selected(index): 48 | button.disabled = false 49 | 50 | 51 | #func _on_size_changed(): 52 | #var size = DisplayServer.window_get_size() 53 | #Globals.BASE_HEIGHT = Globals.BASE_WIDTH / size.x * size.y 54 | -------------------------------------------------------------------------------- /scenes/note_part.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=13 format=3 uid="uid://bimnf7vg6rm4e"] 2 | 3 | [ext_resource type="Script" path="res://scripts/note_part.gd" id="1_5twgq"] 4 | [ext_resource type="Texture2D" uid="uid://dvbneox0gjx4x" path="res://assets/game/Tap.png" id="2_llyil"] 5 | [ext_resource type="Texture2D" uid="uid://dgf20trkxe1f7" path="res://assets/game/Drag.png" id="3_8irbo"] 6 | [ext_resource type="Texture2D" uid="uid://dv3u3b75tovo6" path="res://assets/game/Flick.png" id="4_ap84p"] 7 | [ext_resource type="Texture2D" uid="uid://cb8ox3bigg8gg" path="res://assets/game/HoldHead.png" id="5_5j1ex"] 8 | [ext_resource type="Texture2D" uid="uid://b5u2i6lanbovy" path="res://assets/game/Hold.png" id="6_tejf1"] 9 | [ext_resource type="Texture2D" uid="uid://01xrtichl6li" path="res://assets/game/TapHL.png" id="7_mpnl7"] 10 | [ext_resource type="Texture2D" uid="uid://dnd6ph0sfisdd" path="res://assets/game/DragHL.png" id="8_nqjtq"] 11 | [ext_resource type="Texture2D" uid="uid://vvfr3l8e6x0r" path="res://assets/game/FlickHL.png" id="9_akynx"] 12 | [ext_resource type="Texture2D" uid="uid://ux0k6jcxcmwl" path="res://assets/game/HoldHeadHL.png" id="10_o4ktp"] 13 | [ext_resource type="Texture2D" uid="uid://b03a5x7p3qe2j" path="res://assets/game/HoldHL.png" id="11_hdi80"] 14 | [ext_resource type="Texture2D" uid="uid://dmds61eb2sje2" path="res://assets/game/HoldEnd.png" id="12_hlc50"] 15 | 16 | [node name="NotePart" type="Node2D"] 17 | z_index = -2 18 | z_as_relative = false 19 | script = ExtResource("1_5twgq") 20 | 21 | [node name="Tap" type="Sprite2D" parent="."] 22 | visible = false 23 | texture = ExtResource("2_llyil") 24 | 25 | [node name="Drag" type="Sprite2D" parent="."] 26 | visible = false 27 | texture = ExtResource("3_8irbo") 28 | 29 | [node name="Flick" type="Sprite2D" parent="."] 30 | visible = false 31 | texture = ExtResource("4_ap84p") 32 | 33 | [node name="HoldHead" type="Sprite2D" parent="."] 34 | visible = false 35 | z_index = -1 36 | position = Vector2(0, 25) 37 | texture = ExtResource("5_5j1ex") 38 | 39 | [node name="HoldBody" type="Sprite2D" parent="."] 40 | visible = false 41 | z_index = -1 42 | texture = ExtResource("6_tejf1") 43 | 44 | [node name="TapHL" type="Sprite2D" parent="."] 45 | visible = false 46 | texture = ExtResource("7_mpnl7") 47 | 48 | [node name="DragHL" type="Sprite2D" parent="."] 49 | visible = false 50 | texture = ExtResource("8_nqjtq") 51 | 52 | [node name="FlickHL" type="Sprite2D" parent="."] 53 | visible = false 54 | texture = ExtResource("9_akynx") 55 | 56 | [node name="HoldHeadHL" type="Sprite2D" parent="."] 57 | visible = false 58 | z_index = -1 59 | position = Vector2(0, 49.67) 60 | scale = Vector2(1.025, 1.025) 61 | texture = ExtResource("10_o4ktp") 62 | 63 | [node name="HoldBodyHL" type="Sprite2D" parent="."] 64 | visible = false 65 | z_index = -1 66 | scale = Vector2(1.025, 1.025) 67 | texture = ExtResource("11_hdi80") 68 | 69 | [node name="HoldTail" type="Sprite2D" parent="."] 70 | visible = false 71 | z_index = -1 72 | position = Vector2(0, -25) 73 | texture = ExtResource("12_hlc50") 74 | 75 | [node name="Label" type="Label" parent="."] 76 | visible = false 77 | offset_left = -343.0 78 | offset_right = 341.0 79 | offset_bottom = 273.0 80 | theme_override_font_sizes/font_size = 200 81 | horizontal_alignment = 1 82 | -------------------------------------------------------------------------------- /scenes/level.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://10v6v0p7ds0b"] 2 | 3 | [ext_resource type="Script" path="res://scripts/level.gd" id="1_gupd6"] 4 | [ext_resource type="Shader" path="res://shaders/v_blur.gdshader" id="2_fgv5r"] 5 | [ext_resource type="Shader" path="res://shaders/h_blur.gdshader" id="3_4wf6r"] 6 | 7 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_4it3i"] 8 | shader = ExtResource("2_fgv5r") 9 | shader_parameter/kernel_size = 160 10 | shader_parameter/sigma = 50.0 11 | shader_parameter/darkness = 0.5 12 | 13 | [sub_resource type="ViewportTexture" id="ViewportTexture_fqrwh"] 14 | viewport_path = NodePath("SubViewport/VerticalBlurLayer/SubViewport") 15 | 16 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_1ivm1"] 17 | shader = ExtResource("3_4wf6r") 18 | shader_parameter/kernel_size = 160 19 | shader_parameter/sigma = 50.0 20 | 21 | [sub_resource type="ViewportTexture" id="ViewportTexture_fmoef"] 22 | viewport_path = NodePath("SubViewport") 23 | 24 | [node name="Level" type="Node2D"] 25 | script = ExtResource("1_gupd6") 26 | 27 | [node name="Music" type="AudioStreamPlayer" parent="."] 28 | bus = &"Music" 29 | 30 | [node name="Camera2D" type="Camera2D" parent="."] 31 | 32 | [node name="FPS" type="Label" parent="."] 33 | offset_left = -946.0 34 | offset_top = 460.0 35 | offset_right = -733.0 36 | offset_bottom = 529.0 37 | theme_override_font_sizes/font_size = 40 38 | text = "FPS: 60" 39 | vertical_alignment = 2 40 | 41 | [node name="Accuracy" type="Label" parent="."] 42 | offset_left = -946.0 43 | offset_top = 460.0 44 | offset_right = 947.0 45 | offset_bottom = 529.0 46 | theme_override_font_sizes/font_size = 40 47 | text = "100.00%" 48 | horizontal_alignment = 2 49 | vertical_alignment = 2 50 | 51 | [node name="Score" type="Label" parent="."] 52 | offset_left = -946.0 53 | offset_top = -535.0 54 | offset_right = 947.0 55 | offset_bottom = -466.0 56 | theme_override_font_sizes/font_size = 50 57 | text = "0000000" 58 | horizontal_alignment = 2 59 | 60 | [node name="Combo" type="Label" parent="."] 61 | offset_left = -708.0 62 | offset_top = -539.0 63 | offset_right = 709.0 64 | offset_bottom = -456.0 65 | theme_override_font_sizes/font_size = 60 66 | text = "0" 67 | horizontal_alignment = 1 68 | 69 | [node name="ComboText" type="Label" parent="Combo"] 70 | layout_mode = 0 71 | offset_left = 605.0 72 | offset_top = 70.0 73 | offset_right = 812.0 74 | offset_bottom = 151.0 75 | theme_override_font_sizes/font_size = 20 76 | text = "COMBO" 77 | horizontal_alignment = 1 78 | 79 | [node name="Pause" type="Button" parent="."] 80 | offset_left = -946.0 81 | offset_top = -528.0 82 | offset_right = -872.0 83 | offset_bottom = -454.0 84 | theme_override_font_sizes/font_size = 20 85 | text = "Pause" 86 | 87 | [node name="PauseScreen" type="ColorRect" parent="."] 88 | visible = false 89 | custom_minimum_size = Vector2(1920, 1080) 90 | anchors_preset = 15 91 | anchor_right = 1.0 92 | anchor_bottom = 1.0 93 | grow_horizontal = 2 94 | grow_vertical = 2 95 | color = Color(0, 0, 0, 0.498039) 96 | 97 | [node name="Homepage" type="Button" parent="PauseScreen"] 98 | layout_mode = 0 99 | offset_left = 570.0 100 | offset_top = 487.0 101 | offset_right = 892.0 102 | offset_bottom = 593.0 103 | theme_override_font_sizes/font_size = 50 104 | text = "Homepage" 105 | 106 | [node name="Resume" type="Button" parent="PauseScreen"] 107 | layout_mode = 0 108 | offset_left = 1024.0 109 | offset_top = 487.0 110 | offset_right = 1346.0 111 | offset_bottom = 593.0 112 | theme_override_font_sizes/font_size = 50 113 | text = "Resume" 114 | 115 | [node name="SubViewport" type="SubViewport" parent="."] 116 | size = Vector2i(1920, 1080) 117 | 118 | [node name="VerticalBlurLayer" type="Sprite2D" parent="SubViewport"] 119 | z_index = -6 120 | z_as_relative = false 121 | material = SubResource("ShaderMaterial_4it3i") 122 | position = Vector2(960, 540) 123 | texture = SubResource("ViewportTexture_fqrwh") 124 | 125 | [node name="SubViewport" type="SubViewport" parent="SubViewport/VerticalBlurLayer"] 126 | size = Vector2i(1920, 1080) 127 | 128 | [node name="HorizontalBlurLayer" type="Sprite2D" parent="SubViewport/VerticalBlurLayer/SubViewport"] 129 | z_index = -5 130 | z_as_relative = false 131 | material = SubResource("ShaderMaterial_1ivm1") 132 | position = Vector2(960, 540) 133 | 134 | [node name="Background" type="Sprite2D" parent="."] 135 | z_index = -5 136 | z_as_relative = false 137 | texture = SubResource("ViewportTexture_fmoef") 138 | 139 | [connection signal="finished" from="Music" to="." method="_on_music_finished"] 140 | [connection signal="pressed" from="Pause" to="." method="_on_pause_pressed"] 141 | [connection signal="pressed" from="PauseScreen/Homepage" to="." method="_on_homepage_pressed"] 142 | [connection signal="pressed" from="PauseScreen/Resume" to="." method="_on_resume_pressed"] 143 | -------------------------------------------------------------------------------- /scripts/note_part.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | enum NoteType { TAP = 1, DRAG, HOLD, FLICK } 4 | enum JudgeType { PERFECT, GOOD, BAD, MISS, UNJUDGED, UNINVOLVED } 5 | 6 | signal on_judge(judge: JudgeType, note: Node, position: Vector2, counted: bool, hidden: bool, muted: bool) 7 | 8 | class Note: 9 | var type : NoteType 10 | var time : int 11 | var position_x : float 12 | var hold_time : int 13 | var speed : float 14 | var floor_position : float 15 | 16 | 17 | var note: Note 18 | var judge := JudgeType.UNJUDGED 19 | var data: Dictionary 20 | var is_simultaneous := false 21 | var hold_index: int 22 | var hold_head := false 23 | var hold_tail := false 24 | var bpm: float 25 | 26 | @onready var level = $"../.." 27 | @onready var judgeline = $".." 28 | @onready var label = $Label 29 | 30 | 31 | func load_data(): 32 | note = Note.new() 33 | note.type = int(data["type"]) 34 | note.time = data["time"] 35 | note.position_x = data["positionX"] 36 | note.hold_time = data["holdTime"] 37 | note.speed = data["speed"] 38 | note.floor_position = data["floorPosition"] 39 | 40 | 41 | func _ready(): 42 | bpm = judgeline.bpm 43 | connect("on_judge", level._on_judge) 44 | position.x = note.position_x * 0.05625 * Globals.BASE_WIDTH 45 | scale *= Globals.note_size 46 | match note.type: 47 | NoteType.TAP: 48 | if is_simultaneous: 49 | $TapHL.visible = true 50 | else: 51 | $Tap.visible = true 52 | NoteType.DRAG: 53 | if is_simultaneous: 54 | $DragHL.visible = true 55 | else: 56 | $Drag.visible = true 57 | NoteType.FLICK: 58 | if is_simultaneous: 59 | $FlickHL.visible = true 60 | else: 61 | $Flick.visible = true 62 | NoteType.HOLD: 63 | if hold_head: 64 | if is_simultaneous: 65 | $HoldHeadHL.visible = true 66 | else: 67 | $HoldHead.visible = true 68 | elif hold_tail: 69 | $HoldTail.visible = true 70 | else: 71 | if is_simultaneous: 72 | $HoldBodyHL.visible = true 73 | else: 74 | $HoldBody.visible = true 75 | 76 | 77 | func autoplay(time: float): 78 | if judge != JudgeType.UNJUDGED and judge != JudgeType.UNINVOLVED: 79 | return 80 | judge = JudgeType.PERFECT 81 | visible = false 82 | if note.type != NoteType.HOLD or hold_head: 83 | var scene = load("res://scenes/square.tscn") 84 | for i in 48: 85 | for j in 27: 86 | var pos = Vector2(i, j) * 40 - Vector2(940, 520) 87 | if level.judge_distance(pos, self) <= Globals.judge_radius / 10: 88 | level.put(scene, pos) 89 | #print("Global: (" + str(global_position.x) + ", " + str(global_position.y) + ", " + str(global_rotation) + "); Relative: (" + str(position.x) + ", " + str(position.y) + ", " + str(rotation) + "); Line: (" + str(judgeline.position.x) + ", " + str(judgeline.position.y) + ", " + str(judgeline.rotation) + ");") 90 | var judge_position = judgeline.position + Vector2(cos(judgeline.rotation), sin(judgeline.rotation)) * position.x 91 | if hold_head: 92 | var hold_end = note.time + note.hold_time 93 | var head = true 94 | while judgeline.time < hold_end: 95 | judge_position = judgeline.position + Vector2(cos(judgeline.rotation), sin(judgeline.rotation)) * position.x 96 | on_judge.emit(judge, self, judge_position, false, false, !head) 97 | head = false 98 | await get_tree().create_timer(30 / bpm).timeout 99 | on_judge.emit(judge, self, judge_position, true, true, true) 100 | else: 101 | on_judge.emit(judge, self, judge_position, true, false, false) 102 | 103 | 104 | func handle_input(time_in_seconds: float, event: InputEventFromWindow): 105 | if judge != JudgeType.UNJUDGED and judge != JudgeType.UNINVOLVED: 106 | return 107 | var delta = 1e3 * (time_in_seconds - Globals.cs(note.time, bpm)) 108 | if abs(delta) <= Globals.perfect: 109 | judge = JudgeType.PERFECT 110 | elif abs(delta) <= Globals.good: 111 | judge = JudgeType.GOOD if note.type == NoteType.TAP or note.type == NoteType.HOLD else JudgeType.PERFECT 112 | elif note.type == NoteType.TAP: 113 | judge = JudgeType.BAD 114 | else: 115 | judge = JudgeType.MISS 116 | #print("Global: (" + str(global_position.x) + ", " + str(global_position.y) + ", " + str(global_rotation) + "); Relative: (" + str(position.x) + ", " + str(position.y) + ", " + str(rotation) + "); Line: (" + str(judgeline.position.x) + ", " + str(judgeline.position.y) + ", " + str(judgeline.rotation) + ");") 117 | var judge_position = judgeline.position + Vector2(cos(judgeline.rotation), sin(judgeline.rotation)) * position.x 118 | if hold_head: 119 | var hold_end = note.time + note.hold_time 120 | var head = true 121 | while judgeline.time < hold_end and is_holding(event.index): 122 | judge_position = judgeline.position + Vector2(cos(judgeline.rotation), sin(judgeline.rotation)) * position.x 123 | on_judge.emit(judge, self, judge_position, false, false, !head) 124 | head = false 125 | await get_tree().create_timer(30 / bpm).timeout 126 | if judgeline.time < hold_end - 16: 127 | miss() 128 | else: 129 | on_judge.emit(judge, self, judge_position, true, true, true) 130 | elif note.type == NoteType.TAP: 131 | if abs(delta) <= Globals.good: 132 | visible = false 133 | on_judge.emit(judge, self, judge_position, true, false, false) 134 | else: 135 | await get_tree().create_timer(max(-delta / 1e3, 0)).timeout 136 | visible = false 137 | on_judge.emit(judge, self, judge_position, true, false, false) 138 | 139 | 140 | func is_holding(index: int): 141 | if Globals.finger_lock: 142 | return level.judge_distance(level.fingers[index].position, self) <= Globals.judge_radius 143 | else: 144 | return level.fingers.keys().any(func (i): return level.judge_distance(level.fingers[i].position, self) <= Globals.judge_radius) 145 | 146 | 147 | func miss(): 148 | if judge != JudgeType.UNJUDGED and !hold_head: 149 | return 150 | judge = JudgeType.MISS 151 | if hold_head: 152 | var group = judgeline.hold_groups[hold_index] 153 | group.body.modulate.a = 0.5 154 | group.tail.modulate.a = 0.5 155 | visible = false 156 | #print("Global: (" + str(global_position.x) + ", " + str(global_position.y) + ", " + str(global_rotation) + "); Relative: (" + str(position.x) + ", " + str(position.y) + ", " + str(rotation) + "); Line: (" + str(judgeline.position.x) + ", " + str(judgeline.position.y) + ", " + str(judgeline.rotation) + ");") 157 | var judge_position = judgeline.position + Vector2(cos(judgeline.rotation), sin(judgeline.rotation)) * position.x 158 | on_judge.emit(judge, self, judge_position, true, false, false) 159 | -------------------------------------------------------------------------------- /scenes/hit_effects.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=36 format=3 uid="uid://bfvje0jtpf6l2"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://b3xjyx362yd82" path="res://assets/game/hit_effects/img-1.png" id="1_p5pal"] 4 | [ext_resource type="Texture2D" uid="uid://cjhgqirgqwnyx" path="res://assets/game/hit_effects/img-2.png" id="2_rcip8"] 5 | [ext_resource type="Texture2D" uid="uid://dwi3gxeqm6ksg" path="res://assets/game/hit_effects/img-3.png" id="3_pc3tk"] 6 | [ext_resource type="Texture2D" uid="uid://ch2wjsej1e5xt" path="res://assets/game/hit_effects/img-4.png" id="4_hopvp"] 7 | [ext_resource type="Texture2D" uid="uid://crti061gnh7qf" path="res://assets/game/hit_effects/img-5.png" id="5_6fiee"] 8 | [ext_resource type="Texture2D" uid="uid://bh2fcon4otlt" path="res://assets/game/hit_effects/img-6.png" id="6_ag2g7"] 9 | [ext_resource type="Texture2D" uid="uid://cii3a0krs7o03" path="res://assets/game/hit_effects/img-7.png" id="7_fp3us"] 10 | [ext_resource type="Texture2D" uid="uid://3ehioeaauew" path="res://assets/game/hit_effects/img-8.png" id="8_jbk5e"] 11 | [ext_resource type="Texture2D" uid="uid://cdvuf5in6kb7b" path="res://assets/game/hit_effects/img-9.png" id="9_cydyr"] 12 | [ext_resource type="Texture2D" uid="uid://bpwca4gj2pfk3" path="res://assets/game/hit_effects/img-10.png" id="10_y5w6i"] 13 | [ext_resource type="Texture2D" uid="uid://y0krghx1x8r0" path="res://assets/game/hit_effects/img-11.png" id="11_bbrov"] 14 | [ext_resource type="Texture2D" uid="uid://bypgnd055540t" path="res://assets/game/hit_effects/img-12.png" id="12_qfwup"] 15 | [ext_resource type="Texture2D" uid="uid://bdfqvjv60yxem" path="res://assets/game/hit_effects/img-13.png" id="13_64nba"] 16 | [ext_resource type="Texture2D" uid="uid://cm3x7kgjet6ig" path="res://assets/game/hit_effects/img-14.png" id="14_ru8mc"] 17 | [ext_resource type="Texture2D" uid="uid://dby2e8c8clm8i" path="res://assets/game/hit_effects/img-15.png" id="15_i6ykn"] 18 | [ext_resource type="Texture2D" uid="uid://c7te100bgdgbo" path="res://assets/game/hit_effects/img-16.png" id="16_3eclx"] 19 | [ext_resource type="Texture2D" uid="uid://oho2boalxu17" path="res://assets/game/hit_effects/img-17.png" id="17_xfpng"] 20 | [ext_resource type="Texture2D" uid="uid://ceibjgwawt7cn" path="res://assets/game/hit_effects/img-18.png" id="18_2yykh"] 21 | [ext_resource type="Texture2D" uid="uid://bilis1q466nwu" path="res://assets/game/hit_effects/img-19.png" id="19_8ne1a"] 22 | [ext_resource type="Texture2D" uid="uid://csoebj5fea6em" path="res://assets/game/hit_effects/img-20.png" id="20_j4sv7"] 23 | [ext_resource type="Texture2D" uid="uid://v03mlrai8xq8" path="res://assets/game/hit_effects/img-21.png" id="21_dpnrs"] 24 | [ext_resource type="Texture2D" uid="uid://cnhopc3n8060l" path="res://assets/game/hit_effects/img-22.png" id="22_8jib1"] 25 | [ext_resource type="Texture2D" uid="uid://bb0u0c4jgajoy" path="res://assets/game/hit_effects/img-23.png" id="23_fvqf1"] 26 | [ext_resource type="Texture2D" uid="uid://c4sbff140hm34" path="res://assets/game/hit_effects/img-24.png" id="24_x4mca"] 27 | [ext_resource type="Texture2D" uid="uid://duqp65hym67d1" path="res://assets/game/hit_effects/img-25.png" id="25_fdl1o"] 28 | [ext_resource type="Texture2D" uid="uid://xmnwehsl6pjp" path="res://assets/game/hit_effects/img-26.png" id="26_d32jw"] 29 | [ext_resource type="Texture2D" uid="uid://c0hk5ix8fffiw" path="res://assets/game/hit_effects/img-27.png" id="27_yxxaj"] 30 | [ext_resource type="Texture2D" uid="uid://dix5pbj00spqr" path="res://assets/game/hit_effects/img-28.png" id="28_6w5wo"] 31 | [ext_resource type="Texture2D" uid="uid://dyqb4xki32ywd" path="res://assets/game/hit_effects/img-29.png" id="29_pxv5q"] 32 | [ext_resource type="Texture2D" uid="uid://bnulbdr2munhp" path="res://assets/game/hit_effects/img-30.png" id="30_y26tt"] 33 | [ext_resource type="Texture2D" uid="uid://ba2srcu6mpill" path="res://assets/game/hit_effects/img-31.png" id="31_dja3b"] 34 | [ext_resource type="Script" path="res://scripts/hit_effects.gd" id="32_5i3hl"] 35 | [ext_resource type="Texture2D" uid="uid://pjh3kso1wvw5" path="res://assets/game/hit_effects/square.png" id="33_dttw0"] 36 | [ext_resource type="Script" path="res://scripts/particle.gd" id="34_2n0tm"] 37 | 38 | [sub_resource type="SpriteFrames" id="SpriteFrames_kqjjk"] 39 | animations = [{ 40 | "frames": [{ 41 | "duration": 1.0, 42 | "texture": ExtResource("1_p5pal") 43 | }, { 44 | "duration": 1.0, 45 | "texture": ExtResource("2_rcip8") 46 | }, { 47 | "duration": 1.0, 48 | "texture": ExtResource("3_pc3tk") 49 | }, { 50 | "duration": 1.0, 51 | "texture": ExtResource("4_hopvp") 52 | }, { 53 | "duration": 1.0, 54 | "texture": ExtResource("5_6fiee") 55 | }, { 56 | "duration": 1.0, 57 | "texture": ExtResource("6_ag2g7") 58 | }, { 59 | "duration": 1.0, 60 | "texture": ExtResource("7_fp3us") 61 | }, { 62 | "duration": 1.0, 63 | "texture": ExtResource("8_jbk5e") 64 | }, { 65 | "duration": 1.0, 66 | "texture": ExtResource("9_cydyr") 67 | }, { 68 | "duration": 1.0, 69 | "texture": ExtResource("10_y5w6i") 70 | }, { 71 | "duration": 1.0, 72 | "texture": ExtResource("11_bbrov") 73 | }, { 74 | "duration": 1.0, 75 | "texture": ExtResource("12_qfwup") 76 | }, { 77 | "duration": 1.0, 78 | "texture": ExtResource("13_64nba") 79 | }, { 80 | "duration": 1.0, 81 | "texture": ExtResource("14_ru8mc") 82 | }, { 83 | "duration": 1.0, 84 | "texture": ExtResource("15_i6ykn") 85 | }, { 86 | "duration": 1.0, 87 | "texture": ExtResource("16_3eclx") 88 | }, { 89 | "duration": 1.0, 90 | "texture": ExtResource("17_xfpng") 91 | }, { 92 | "duration": 1.0, 93 | "texture": ExtResource("18_2yykh") 94 | }, { 95 | "duration": 1.0, 96 | "texture": ExtResource("19_8ne1a") 97 | }, { 98 | "duration": 1.0, 99 | "texture": ExtResource("20_j4sv7") 100 | }, { 101 | "duration": 1.0, 102 | "texture": ExtResource("21_dpnrs") 103 | }, { 104 | "duration": 1.0, 105 | "texture": ExtResource("22_8jib1") 106 | }, { 107 | "duration": 1.0, 108 | "texture": ExtResource("23_fvqf1") 109 | }, { 110 | "duration": 1.0, 111 | "texture": ExtResource("24_x4mca") 112 | }, { 113 | "duration": 1.0, 114 | "texture": ExtResource("25_fdl1o") 115 | }, { 116 | "duration": 1.0, 117 | "texture": ExtResource("26_d32jw") 118 | }, { 119 | "duration": 1.0, 120 | "texture": ExtResource("27_yxxaj") 121 | }, { 122 | "duration": 1.0, 123 | "texture": ExtResource("28_6w5wo") 124 | }, { 125 | "duration": 1.0, 126 | "texture": ExtResource("29_pxv5q") 127 | }, { 128 | "duration": 1.0, 129 | "texture": ExtResource("30_y26tt") 130 | }, { 131 | "duration": 1.0, 132 | "texture": ExtResource("31_dja3b") 133 | }], 134 | "loop": false, 135 | "name": &"default", 136 | "speed": 60.0 137 | }] 138 | 139 | [node name="HitEffects" type="AnimatedSprite2D"] 140 | z_index = -1 141 | z_as_relative = false 142 | scale = Vector2(1.32, 1.32) 143 | sprite_frames = SubResource("SpriteFrames_kqjjk") 144 | autoplay = "default" 145 | script = ExtResource("32_5i3hl") 146 | 147 | [node name="Particle1" type="Sprite2D" parent="."] 148 | scale = Vector2(1e-05, 1e-05) 149 | texture = ExtResource("33_dttw0") 150 | script = ExtResource("34_2n0tm") 151 | 152 | [node name="Particle2" type="Sprite2D" parent="."] 153 | scale = Vector2(1e-05, 1e-05) 154 | texture = ExtResource("33_dttw0") 155 | script = ExtResource("34_2n0tm") 156 | 157 | [node name="Particle3" type="Sprite2D" parent="."] 158 | scale = Vector2(1e-05, 1e-05) 159 | texture = ExtResource("33_dttw0") 160 | script = ExtResource("34_2n0tm") 161 | 162 | [node name="Particle4" type="Sprite2D" parent="."] 163 | scale = Vector2(1e-05, 1e-05) 164 | texture = ExtResource("33_dttw0") 165 | script = ExtResource("34_2n0tm") 166 | 167 | [connection signal="animation_finished" from="." to="." method="_on_animation_finished"] 168 | -------------------------------------------------------------------------------- /scripts/judgeline.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const NOTE_SCENE := "res://scenes/note_part.tscn" 4 | const NotePart = preload("res://scripts/note_part.gd") 5 | 6 | class Event: 7 | var start_time: float 8 | var end_time: float 9 | 10 | class SpeedEvent extends Event: 11 | var value: float 12 | 13 | class DurationalEvent extends Event: 14 | var start: float 15 | var end: float 16 | 17 | class DurationalVectorEvent extends Event: 18 | var start: float 19 | var end: float 20 | var start2: float 21 | var end2: float 22 | 23 | class HoldGroup: 24 | var head: Node 25 | var body: Node 26 | var tail: Node 27 | 28 | 29 | var notes_above: Array[Node] 30 | var notes_below: Array[Node] 31 | var speed_events: Array[SpeedEvent] 32 | var move_events: Array[DurationalVectorEvent] 33 | var rotate_events: Array[DurationalEvent] 34 | var disappear_events: Array[DurationalEvent] 35 | var bpm: float 36 | 37 | var time: float 38 | var current_move := 0 39 | var current_rotate := 0 40 | var current_disappear := 0 41 | var current_speed := 0 42 | var current_height := 0 43 | 44 | var data: Dictionary 45 | var line_number: int 46 | var total_time: int 47 | var height_time: Array[float] 48 | var height_time_cache := {} 49 | var height_cache: Array[float] 50 | var hold_groups: Array[HoldGroup] 51 | 52 | @onready var texture = $Texture 53 | @onready var label = $Label 54 | 55 | 56 | func _ready(): 57 | label.text = str(line_number) 58 | for note in notes_above: 59 | add_child(note) 60 | for note in notes_below: 61 | add_child(note) 62 | 63 | 64 | func load_judgeline(line_no: int): 65 | line_number = line_no 66 | bpm = data["bpm"] 67 | notes_above = load_notes(data["notesAbove"]) 68 | notes_below = load_notes(data["notesBelow"]) 69 | speed_events = load_speed_events(data["speedEvents"]) 70 | move_events = load_durational_vector_events(data["judgeLineMoveEvents"]) 71 | rotate_events = load_durational_events(data["judgeLineRotateEvents"]) 72 | disappear_events = load_durational_events(data["judgeLineDisappearEvents"]) 73 | 74 | 75 | func draw(time_in_seconds, delta): 76 | time = Globals.ct(time_in_seconds, bpm) 77 | handle_moves(time) 78 | handle_rotations(time) 79 | handle_opacities(time) 80 | handle_heights(time) 81 | handle_notes(time, notes_above, 1) 82 | handle_notes(time, notes_below, -1) 83 | 84 | 85 | func handle_moves(time): 86 | if move_events.size() == 0: 87 | return 88 | var event = move_events[current_move] 89 | var progress = clampf((time - event.start_time) / (event.end_time - event.start_time), 0, 1) 90 | position.x = Globals.BASE_WIDTH * (progress * (event.end - event.start) + event.start) - Globals.BASE_WIDTH / 2 91 | position.y = -Globals.BASE_HEIGHT * (progress * (event.end2 - event.start2) + event.start2) + Globals.BASE_HEIGHT / 2 92 | while time >= move_events[current_move].end_time && current_move < move_events.size() - 1: 93 | current_move += 1 94 | 95 | 96 | func handle_rotations(time): 97 | if rotate_events.size() == 0: 98 | return 99 | var event = rotate_events[current_rotate] 100 | var progress = clampf((time - event.start_time) / (event.end_time - event.start_time), 0, 1) 101 | rotation_degrees = -(progress * (event.end - event.start) + event.start) 102 | while time >= rotate_events[current_rotate].end_time && current_rotate < rotate_events.size() - 1: 103 | current_rotate += 1 104 | 105 | 106 | func handle_opacities(time): 107 | if disappear_events.size() == 0: 108 | return 109 | var event = disappear_events[current_disappear] 110 | var progress = clampf((time - event.start_time) / (event.end_time - event.start_time), 0, 1) 111 | texture.self_modulate.a = progress * (event.end - event.start) + event.start 112 | while time >= disappear_events[current_disappear].end_time && current_disappear < disappear_events.size() - 1: 113 | current_disappear += 1 114 | 115 | 116 | func handle_notes(time, notes, modifier: int): 117 | for note in notes: 118 | if (!note.visible or note.modulate.a == 0 or note.scale.y == 0) and note.judge != note.JudgeType.UNJUDGED \ 119 | and note.judge != note.JudgeType.UNINVOLVED and note.note.time + note.note.hold_time < time - 32: 120 | notes.erase(note) 121 | $"..".notes.erase(note) 122 | #note.queue_free() 123 | continue 124 | if note.note.time < time + Globals.EPS: 125 | if note.note.type != note.NoteType.HOLD or note.hold_head or note.note.time + note.note.hold_time <= time: 126 | if Globals.is_autoplay: 127 | note.autoplay(time) 128 | else: 129 | var delta = 1e3 * Globals.cs(time - note.note.time, bpm) 130 | note.modulate.a = max(0, 1 - delta / Globals.good) if note.note.type != note.NoteType.HOLD else 0 131 | if delta > Globals.good and note.judge == note.JudgeType.UNJUDGED: 132 | note.miss() 133 | if (note.note.time > time or !note.hold_head) and note.judge != note.JudgeType.BAD: 134 | if note.note.type != note.NoteType.HOLD or note.hold_head or note.hold_tail: 135 | note.scale = Vector2.ONE * Globals.note_size * modifier 136 | if note.judge == note.JudgeType.UNJUDGED or note.judge == note.JudgeType.UNINVOLVED: 137 | note.visible = note.position.y / modifier <= 0 or note.note.time < time + Globals.EPS 138 | if note.hold_head: 139 | note.position.y = -calculate_distance(time, note.note.time) * modifier 140 | elif note.hold_tail: 141 | note.position.y = -calculate_distance(time, note.note.time + note.note.hold_time) * modifier 142 | else: 143 | note.position.y = -calculate_distance(time, note.note.time) * modifier * note.note.speed 144 | else: 145 | var start_height = -calculate_distance(time, max(note.note.time, time)) * modifier 146 | var end_height = -calculate_distance(time, note.note.time + note.note.hold_time) * modifier 147 | if start_height / modifier > 0: 148 | start_height = 0 149 | if end_height / modifier > 0: 150 | end_height = 0 151 | note.scale.x = Globals.note_size * modifier 152 | note.scale.y = (start_height - end_height) / 1900 153 | note.position.y = (start_height + end_height) / 2 154 | 155 | 156 | func calculate_distance(time, note_time) -> float: 157 | if is_equal_approx(time, note_time): 158 | return 0 159 | var modifier = 1 160 | if time > note_time: 161 | var temp = note_time 162 | note_time = time 163 | time = temp 164 | modifier = -1 165 | var end_index: int 166 | if height_time_cache.has(note_time): 167 | end_index = height_time_cache[note_time] 168 | else: 169 | end_index = height_time.bsearch(note_time) 170 | height_time_cache[note_time] = end_index 171 | var remainder_end: float 172 | if end_index >= height_time.size(): 173 | var event = speed_events[height_time.size() - 2] 174 | remainder_end = (note_time - event.start_time) * event.value 175 | else: 176 | remainder_end = (height_cache[end_index] - (height_cache[end_index - 1])) \ 177 | * (note_time - height_time[end_index - 1]) / (height_time[end_index] - height_time[end_index - 1]) 178 | var remainder_start = (height_cache[current_height + 1] - height_cache[current_height]) \ 179 | * (height_time[current_height + 1] - time) / (height_time[current_height + 1] - height_time[current_height]) 180 | var result = Globals.cs(modifier * (remainder_start + remainder_end + height_cache[end_index - 1] - height_cache[current_height + 1]) \ 181 | * 0.6 * Globals.BASE_HEIGHT, bpm) 182 | return result 183 | 184 | 185 | func handle_heights(time): 186 | while current_height < height_time.size() - 1 && time >= height_time[current_height + 1]: 187 | current_height += 1 188 | 189 | # 纯暴力 190 | #func calculate_distance(time, note) -> float: 191 | #var distance := 0.0 192 | #for i in speed_events.size() - current_speed: 193 | #var event = speed_events[i + current_speed] 194 | #if event.start_time > note.note.time: 195 | #break 196 | #var start_time = max(time, event.start_time) 197 | #var end_time: float 198 | #if i == speed_events.size() - current_speed - 1: 199 | #end_time = note.note.time 200 | #else: 201 | #end_time = min(note.note.time, speed_events[i + current_speed + 1].start_time) 202 | #distance += Globals.cs((end_time - start_time) * event.value) 203 | #if current_speed < speed_events.size() - 1 && time >= speed_events[current_speed + 1].start_time: 204 | #current_speed += 1 205 | #return 0.6 * Globals.BASE_HEIGHT * distance * note.note.speed 206 | 207 | 208 | func load_notes(data) -> Array[Node]: 209 | var result: Array[Node] = [] 210 | var scene = load(NOTE_SCENE) 211 | for note_data in data: 212 | var note_part = scene.instantiate() 213 | note_part.data = note_data 214 | note_part.load_data() 215 | if note_part.note.type == NotePart.NoteType.HOLD: 216 | var hold_head = scene.instantiate() 217 | hold_head.data = note_data 218 | hold_head.hold_head = true 219 | hold_head.load_data() 220 | var hold_tail = scene.instantiate() 221 | hold_tail.data = note_data 222 | hold_tail.hold_tail = true 223 | hold_tail.load_data() 224 | note_part.judge = note_part.JudgeType.UNINVOLVED 225 | hold_tail.judge = note_part.JudgeType.UNINVOLVED 226 | hold_head.hold_index = hold_groups.size() 227 | note_part.hold_index = hold_groups.size() 228 | hold_tail.hold_index = hold_groups.size() 229 | var group = HoldGroup.new() 230 | group.head = hold_head 231 | group.body = note_part 232 | group.tail = hold_tail 233 | hold_groups.push_back(group) 234 | result.append(hold_head) 235 | result.append(hold_tail) 236 | result.append(note_part) 237 | return result 238 | 239 | 240 | func load_speed_events(data) -> Array[SpeedEvent]: 241 | var result: Array[SpeedEvent] = [] 242 | for event_data in data: 243 | var event = SpeedEvent.new() 244 | event.start_time = event_data["startTime"] 245 | event.end_time = event_data["endTime"] 246 | event.value = event_data["value"] 247 | result.append(event) 248 | if result[-1].end_time < total_time: 249 | result[-1].end_time = total_time 250 | result.sort_custom(func (a, b): return a.start_time < b.start_time) 251 | var height := 0 252 | height_time.append(0) 253 | height_cache.append(0) 254 | for event in result: 255 | height_time.append(event.end_time) 256 | height += (event.end_time - event.start_time) * event.value 257 | height_cache.append(height) 258 | return result 259 | 260 | 261 | func load_durational_events(data) -> Array[DurationalEvent]: 262 | var result: Array[DurationalEvent] = [] 263 | for event_data in data: 264 | var event = DurationalEvent.new() 265 | event.start_time = event_data["startTime"] 266 | event.end_time = event_data["endTime"] 267 | event.start = event_data["start"] 268 | event.end = event_data["end"] 269 | result.append(event) 270 | result.sort_custom(func (a, b): return a.start_time < b.start_time) 271 | return result 272 | 273 | 274 | func load_durational_vector_events(data) -> Array[DurationalVectorEvent]: 275 | var result: Array[DurationalVectorEvent] = [] 276 | for event_data in data: 277 | var event = DurationalVectorEvent.new() 278 | event.start_time = event_data["startTime"] 279 | event.end_time = event_data["endTime"] 280 | event.start = event_data["start"] 281 | event.end = event_data["end"] 282 | event.start2 = event_data["start2"] 283 | event.end2 = event_data["end2"] 284 | result.append(event) 285 | result.sort_custom(func (a, b): return a.start_time < b.start_time) 286 | return result 287 | 288 | 289 | func implement_simultaneous_hints(moments: Array[int]): 290 | for note in notes_above: 291 | if moments.any(func (element): return element == note.note.time): 292 | note.is_simultaneous = true 293 | for note in notes_below: 294 | if moments.any(func (element): return element == note.note.time): 295 | note.is_simultaneous = true 296 | -------------------------------------------------------------------------------- /scripts/level.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const JUDGELINE_SCENE = "res://scenes/judgeline.tscn" 4 | const HIT_EFFECTS_SCENE = "res://scenes/hit_effects.tscn" 5 | const HIT_SOUND_SCENE = "res://scenes/hit_sound.tscn" 6 | const TAP_HIT = "res://assets/game/tap.wav" 7 | const DRAG_HIT = "res://assets/game/drag.wav" 8 | const FLICK_HIT = "res://assets/game/flick.wav" 9 | const PERFECT = Color8(255, 255, 180) 10 | const GOOD = Color8(179, 236, 255) 11 | const WHITE = Color8(255, 255, 255) 12 | const NotePart = preload("res://scripts/note_part.gd") 13 | 14 | @onready var music = $Music 15 | @onready var background_temp = $SubViewport/VerticalBlurLayer/SubViewport/HorizontalBlurLayer 16 | @onready var background = $Background 17 | @onready var pause_screen = $PauseScreen 18 | 19 | var path: String 20 | var level: String 21 | var notes: Array[Node] 22 | var note_count: int 23 | var offset := 0 24 | 25 | var tap_hit: Resource 26 | var drag_hit: Resource 27 | var flick_hit: Resource 28 | 29 | var time_in_seconds: float 30 | var bpm: float 31 | var judgelines: Array[Node] 32 | var hit_effects_pool: Array[Node] 33 | var hit_sound_pool: Array[Node] 34 | var fc_ap_status := 2 35 | var color := PERFECT 36 | var score := 0 37 | var accuracy := 1. 38 | var max_combo := 0 39 | var combo := 0 40 | var perfect := 0 41 | var good := 0 42 | var bad := 0 43 | var miss := 0 44 | 45 | var fingers := {} 46 | var finger_resistance := {} 47 | 48 | 49 | func _ready(): 50 | var chart = path + "/" + "Chart_" + level + ".json" 51 | var song = path + "/" + "music.ogg" 52 | var illustration = path + "/" + "Illustration.png" 53 | background_temp.texture = load(illustration) 54 | $SubViewport/VerticalBlurLayer.material.set("shader_parameter/darkness", Globals.bg_darkness) 55 | var thread = Thread.new() 56 | thread.start(func (): 57 | await get_tree().create_timer(0.5).timeout 58 | var image = background.texture.get_image() 59 | background.texture = ImageTexture.create_from_image(image) 60 | $SubViewport/VerticalBlurLayer.visible = false 61 | ) 62 | music.stream = load(song) 63 | tap_hit = load(TAP_HIT) 64 | drag_hit = load(DRAG_HIT) 65 | flick_hit = load(FLICK_HIT) 66 | load_chart(chart) 67 | preprocess() 68 | for judgeline in judgelines: 69 | add_child(judgeline) 70 | music.play() 71 | 72 | 73 | func _process(delta): 74 | $FPS.text = "FPS: " + str(int(1 / delta)) 75 | $Combo.text = str(combo) 76 | $Score.text = "%07d" % score 77 | $Accuracy.text = "AUTOPLAY" if Globals.is_autoplay else ("%.2f" % (roundf(accuracy * 1e4) / 1e2) + "%") 78 | if music.playing: 79 | time_in_seconds = music.get_playback_position() - offset / 1000 80 | for judgeline in judgelines: 81 | judgeline.draw(time_in_seconds, delta) 82 | for judgeline in judgelines: 83 | judgeline.texture.self_modulate.r = color.r 84 | judgeline.texture.self_modulate.g = color.g 85 | judgeline.texture.self_modulate.b = color.b 86 | #judgeline.label.self_modulate = judgeline.texture.self_modulate 87 | for i in fingers: 88 | handle_drag(fingers[i]) 89 | handle_flick(fingers[i]) 90 | 91 | 92 | func load_chart(file_name): 93 | var content = FileAccess.get_file_as_string(file_name) 94 | var data = JSON.parse_string(content) 95 | offset = data["offset"] 96 | bpm = data["judgeLineList"][0]["bpm"] 97 | var scene = load(JUDGELINE_SCENE) 98 | var index = 0 99 | for judgeline_data in data["judgeLineList"]: 100 | print("Loading Line #" + str(index)) 101 | var judgeline = scene.instantiate() 102 | judgeline.data = judgeline_data 103 | var temp_bpm = data["judgeLineList"][index]["bpm"] 104 | judgeline.total_time = Globals.ct(music.stream.get_length(), temp_bpm) 105 | judgeline.load_judgeline(index) 106 | judgelines.append(judgeline) 107 | index += 1 108 | 109 | 110 | func preprocess(): 111 | var temp_notes: Array[Node] 112 | for judgeline in judgelines: 113 | temp_notes.append_array(judgeline.notes_above) 114 | temp_notes.append_array(judgeline.notes_below) 115 | temp_notes.sort_custom(func (a, b): return a.note.time < b.note.time) 116 | var simultaneous_moments: Array[int] 117 | var last_moment := -1 118 | var hit_effects_scene = load(HIT_EFFECTS_SCENE) 119 | var hit_sound_scene = load(HIT_SOUND_SCENE) 120 | for note in temp_notes: 121 | hit_effects_pool.push_back(hit_effects_scene.instantiate()) 122 | hit_sound_pool.push_back(hit_sound_scene.instantiate()) 123 | if note.note.type == note.NoteType.HOLD and !note.hold_head: 124 | continue 125 | notes.push_back(note) 126 | note_count += 1 127 | if note.hold_head: 128 | for i in note.note.hold_time / 16: 129 | hit_effects_pool.push_back(hit_effects_scene.instantiate()) 130 | if note.note.time == last_moment: 131 | simultaneous_moments.append(note.note.time) 132 | else: 133 | last_moment = note.note.time 134 | var index = 0 135 | for judgeline in judgelines: 136 | judgeline.implement_simultaneous_hints(simultaneous_moments) 137 | index += 1 138 | 139 | 140 | func judge(type): 141 | match type: 142 | NotePart.JudgeType.PERFECT: 143 | perfect += 1 144 | combo += 1 145 | NotePart.JudgeType.GOOD: 146 | good += 1 147 | combo += 1 148 | NotePart.JudgeType.BAD: 149 | bad += 1 150 | combo = 0 151 | NotePart.JudgeType.MISS: 152 | miss += 1 153 | combo = 0 154 | max_combo = max(max_combo, combo) 155 | if type == NotePart.JudgeType.GOOD: 156 | fc_ap_status = min(fc_ap_status, 1) 157 | elif type != NotePart.JudgeType.PERFECT: 158 | fc_ap_status = min(fc_ap_status, 0) 159 | match fc_ap_status: 160 | 2: 161 | color = PERFECT 162 | 1: 163 | color = GOOD 164 | 0: 165 | color = WHITE 166 | calculate_score() 167 | calculate_accuracy() 168 | 169 | 170 | func calculate_score(): 171 | if note_count == 0: 172 | score = 1_000_000 173 | score = roundi((9e5 * perfect + 585e3 * good + 1e5 * max_combo) / note_count) 174 | 175 | 176 | func calculate_accuracy(): 177 | if note_count == 0: 178 | accuracy = 1. 179 | accuracy = (perfect + 0.65 * good) / (perfect + good + bad + miss) 180 | 181 | 182 | func home(): 183 | var homepage = load("res://scenes/homepage.tscn").instantiate() 184 | queue_free() 185 | get_tree().root.add_child(homepage) 186 | 187 | 188 | func _on_judge(type, note, position, counted, hidden, muted): 189 | print("Judge " + NotePart.JudgeType.keys()[type] + " from " + NotePart.NoteType.keys()[note.note.type - 1] + " received at (" + str(int(position.x)) + ", " + str(int(position.y)) + ")") 190 | if counted: 191 | judge(type) 192 | if !muted and (type == NotePart.JudgeType.PERFECT or type == NotePart.JudgeType.GOOD): 193 | var hit_sound = hit_sound_pool.pop_front() 194 | match note.note.type: 195 | NotePart.NoteType.TAP: 196 | hit_sound.stream = tap_hit 197 | NotePart.NoteType.HOLD: 198 | hit_sound.stream = tap_hit 199 | NotePart.NoteType.DRAG: 200 | hit_sound.stream = drag_hit 201 | NotePart.NoteType.FLICK: 202 | hit_sound.stream = flick_hit 203 | hit_sound.position = position if Globals.is_in_screen(position) else Vector2(clampf(position.x, -Globals.BASE_WIDTH / 2, Globals.BASE_WIDTH / 2), 0) 204 | add_child(hit_sound) 205 | hit_sound.play() 206 | if !hidden: 207 | if type == NotePart.JudgeType.PERFECT or type == NotePart.JudgeType.GOOD: 208 | var hit_effects = hit_effects_pool.pop_front() 209 | hit_effects.position = position 210 | match type: 211 | NotePart.JudgeType.PERFECT: 212 | hit_effects.modulate = PERFECT 213 | NotePart.JudgeType.GOOD: 214 | hit_effects.modulate = GOOD 215 | add_child(hit_effects) 216 | elif type == NotePart.JudgeType.BAD: 217 | note.modulate = Color(1, 0, 0) 218 | var tween = create_tween().set_trans(Tween.TRANS_CUBIC) 219 | tween.tween_property(note, "modulate:a", 0, 0.5).set_ease(Tween.EASE_IN) 220 | 221 | 222 | func _on_music_finished(): 223 | home() 224 | 225 | 226 | func _on_pause_pressed(): 227 | music.stream_paused = true 228 | pause_screen.visible = true 229 | 230 | 231 | func _on_homepage_pressed(): 232 | home() 233 | 234 | 235 | func _on_resume_pressed(): 236 | pause_screen.visible = false 237 | music.stream_paused = false 238 | 239 | 240 | func _unhandled_input(event): 241 | if !(event is InputEventFromWindow): 242 | return 243 | if "position" in event: 244 | event.position -= Vector2(Globals.BASE_WIDTH, Globals.BASE_HEIGHT) / 2 245 | if event is InputEventScreenTouch: 246 | if event.pressed: 247 | fingers[event.index] = event 248 | handle_touch(event) 249 | else: 250 | fingers.erase(event.index) 251 | finger_resistance.erase(event.index) 252 | print(fingers) 253 | elif event is InputEventScreenDrag: 254 | if fingers.has(event.index): 255 | fingers[event.index] = event 256 | print(fingers) 257 | 258 | 259 | func handle_touch(event): 260 | if Globals.is_autoplay: 261 | return 262 | var nearby_notes = notes.filter(func (note): 263 | #if Globals.is_in_screen(note.global_position): 264 | #print(str(event.position) + " -> " + str(note.global_position) + ": " + str(int(note.global_position.distance_to(event.position)))) 265 | #print(time_in_seconds - Globals.cs(note.note.time, note.bpm)) 266 | return note.judge == note.JudgeType.UNJUDGED and (note.note.type == note.NoteType.TAP or note.NoteType.DRAG or note.hold_head) \ 267 | and judge_distance(event.position, note) <= Globals.judge_radius and Globals.is_in((time_in_seconds - Globals.cs(note.note.time, note.bpm)) * 1e3, -Globals.bad, Globals.good)) 268 | print("Nearby Notes: " + str(nearby_notes.size())) 269 | if nearby_notes.size() == 0: 270 | return 271 | #for note in nearby_notes: 272 | #note.modulate.r = 0 273 | #var thread = Thread.new() 274 | #thread.start(func (): 275 | #await get_tree().create_timer(0.3).timeout 276 | #if note: 277 | #note.modulate.r = 1 278 | #) 279 | var nearest_note = nearby_notes.filter(func (note): return note.note.type != note.NoteType.DRAG).reduce(func(accum, cur): 280 | return cur if space_time_distance_sq(event.position, cur, time_in_seconds) < space_time_distance_sq(event.position, accum, time_in_seconds) else accum 281 | ) 282 | if !nearest_note: 283 | return 284 | nearest_note.handle_input(time_in_seconds, event) 285 | 286 | 287 | func handle_drag(event): 288 | if Globals.is_autoplay: 289 | return 290 | var nearby_notes = notes.filter(func (note): 291 | #if Globals.is_in_screen(note.global_position): 292 | #print(str(event.position) + " -> " + str(note.global_position) + ": " + str(int(note.global_position.distance_to(event.position)))) 293 | #print(time_in_seconds - Globals.cs(note.note.time, note.bpm)) 294 | return note.judge == note.JudgeType.UNJUDGED and (note.note.type == note.NoteType.DRAG) \ 295 | and judge_distance(event.position, note) <= Globals.judge_radius and Globals.is_in((time_in_seconds - Globals.cs(note.note.time, note.bpm)) * 1e3, -Globals.good, Globals.good)) 296 | print("Nearby Drags: " + str(nearby_notes.size())) 297 | if nearby_notes.size() == 0: 298 | return 299 | #for note in nearby_notes: 300 | #note.modulate.g = 0 301 | #var thread = Thread.new() 302 | #thread.start(func (): 303 | #await get_tree().create_timer(0.3).timeout 304 | #if note: 305 | #note.modulate.g = 1 306 | #) 307 | var nearest_note = nearby_notes.reduce(func(accum, cur): 308 | return cur if space_time_distance_sq(event.position, cur, time_in_seconds) < space_time_distance_sq(event.position, accum, time_in_seconds) else accum 309 | ) 310 | nearest_note.handle_input(time_in_seconds, event) 311 | 312 | 313 | func handle_flick(event): 314 | if Globals.is_autoplay or !(event is InputEventScreenDrag): 315 | return 316 | var velocity = event.velocity if !finger_resistance.has(event.index) else apply_resistance(event.velocity, finger_resistance[event.index]) 317 | if velocity.length() < Globals.flick_velocity: 318 | return 319 | var nearby_notes = notes.filter(func (note): 320 | #if Globals.is_in_screen(note.global_position): 321 | #print(str(event.position) + " -> " + str(note.global_position) + ": " + str(int(note.global_position.distance_to(event.position)))) 322 | #print(time_in_seconds - Globals.cs(note.note.time, note.bpm)) 323 | return note.judge == note.JudgeType.UNJUDGED and (note.note.type == note.NoteType.FLICK) \ 324 | and judge_distance(event.position, note) <= Globals.judge_radius and Globals.is_in((time_in_seconds - Globals.cs(note.note.time, note.bpm)) * 1e3, -Globals.good, Globals.good)) 325 | print("Nearby Flicks: " + str(nearby_notes.size())) 326 | if nearby_notes.size() == 0: 327 | return 328 | #for note in nearby_notes: 329 | #note.modulate.b = 0 330 | #var thread = Thread.new() 331 | #thread.start(func (): 332 | #await get_tree().create_timer(0.3).timeout 333 | #if note: 334 | #note.modulate.b = 1 335 | #) 336 | var nearest_note = nearby_notes.reduce(func(accum, cur): 337 | return cur if space_time_distance_sq(event.position, cur, time_in_seconds) < space_time_distance_sq(event.position, accum, time_in_seconds) else accum 338 | ) 339 | nearest_note.handle_input(time_in_seconds, event) 340 | finger_resistance[event.index] = event.velocity.angle() 341 | 342 | 343 | func apply_resistance(vector: Vector2, angle: float) -> Vector2: 344 | return vector * max(abs(vector.angle() - angle) / (PI / 2), 1) 345 | 346 | 347 | func judge_distance(position: Vector2, note: Node): 348 | var position_delta = position - note.judgeline.position 349 | return abs(position.x * cos(note.judgeline.rotation) + position.y * sin(note.judgeline.rotation) * Globals.BASE_WIDTH / Globals.BASE_HEIGHT - note.position.x) 350 | 351 | 352 | func space_time_distance_sq(position: Vector2, note: Node, time_in_seconds: float): 353 | var time_delta = time_in_seconds - Globals.cs(note.note.time, note.bpm) 354 | return judge_distance(position, note) ** 2 + time_delta ** 2 355 | 356 | 357 | func put(scene, pos): 358 | var square = scene.instantiate() 359 | square.position = pos 360 | add_child(square) 361 | var tween = create_tween().set_trans(Tween.TRANS_SINE) 362 | tween.tween_property(square, "modulate:a", 0, 0.5).set_ease(Tween.EASE_IN) 363 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------