forked from wxm/Fighting_Rthythm_game
Initial commit: Fighting_Rthythm_game project setup
This commit is contained in:
BIN
scenes/.DS_Store
vendored
Normal file
BIN
scenes/.DS_Store
vendored
Normal file
Binary file not shown.
114
scenes/characters/character.gd
Normal file
114
scenes/characters/character.gd
Normal file
@@ -0,0 +1,114 @@
|
||||
class_name Character
|
||||
extends CharacterBody2D
|
||||
|
||||
const GRAVITY := 1200.0
|
||||
|
||||
@export var speed := 180.0
|
||||
@export var jump_intensity := 304.056
|
||||
@export var attack_duration := 0.4
|
||||
@export var attack_lunge_duration := 0.18
|
||||
@export var attack_lunge_speed := 220.0
|
||||
|
||||
@onready var animation_player: AnimationPlayer = $AnimationPlayer
|
||||
@onready var character_sprite: Sprite2D = $CharacterSprite
|
||||
|
||||
enum State { IDLE, WALK, JUMP, LAND, ATTACK }
|
||||
|
||||
var anim_map := {
|
||||
State.IDLE: "idle",
|
||||
State.WALK: "idle",
|
||||
State.JUMP: "jump",
|
||||
State.LAND: "idle",
|
||||
State.ATTACK: "挥砍",
|
||||
}
|
||||
var attack_direction := Vector2.RIGHT
|
||||
var attack_lunge_time_left := 0.0
|
||||
var attack_time_left := 0.0
|
||||
var heading := Vector2.RIGHT
|
||||
var height := 0.0
|
||||
var height_speed := 0.0
|
||||
var state := State.IDLE
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
handle_input()
|
||||
handle_air_time(delta)
|
||||
handle_attack_time(delta)
|
||||
handle_movement()
|
||||
handle_animations()
|
||||
set_sprite_height_position()
|
||||
set_heading()
|
||||
flip_sprites()
|
||||
move_and_slide()
|
||||
|
||||
func handle_input() -> void:
|
||||
pass
|
||||
|
||||
func handle_air_time(delta: float) -> void:
|
||||
if state != State.JUMP:
|
||||
return
|
||||
height += height_speed * delta
|
||||
if height <= 0.0 and height_speed < 0.0:
|
||||
height = 0.0
|
||||
height_speed = 0.0
|
||||
state = State.LAND
|
||||
else:
|
||||
height_speed -= GRAVITY * delta
|
||||
|
||||
func handle_attack_time(delta: float) -> void:
|
||||
if state != State.ATTACK:
|
||||
return
|
||||
velocity.y = 0.0
|
||||
attack_time_left -= delta
|
||||
if attack_time_left <= 0.0:
|
||||
state = State.IDLE
|
||||
velocity = Vector2.ZERO
|
||||
return
|
||||
attack_lunge_time_left -= delta
|
||||
if attack_lunge_time_left <= 0.0:
|
||||
velocity.x = 0.0
|
||||
|
||||
func handle_movement() -> void:
|
||||
if state == State.JUMP or state == State.ATTACK:
|
||||
return
|
||||
if absf(velocity.x) > 0.0:
|
||||
state = State.WALK
|
||||
else:
|
||||
state = State.IDLE
|
||||
if state == State.LAND:
|
||||
state = State.IDLE
|
||||
|
||||
func handle_animations() -> void:
|
||||
var animation_name: String = anim_map[state]
|
||||
if animation_player.has_animation(animation_name) and animation_player.current_animation != animation_name:
|
||||
animation_player.play(animation_name)
|
||||
|
||||
func set_sprite_height_position() -> void:
|
||||
character_sprite.position = Vector2.UP * height
|
||||
|
||||
func set_heading() -> void:
|
||||
pass
|
||||
|
||||
func flip_sprites() -> void:
|
||||
character_sprite.flip_h = heading == Vector2.LEFT
|
||||
|
||||
func can_jump() -> bool:
|
||||
return state == State.IDLE or state == State.WALK
|
||||
|
||||
func can_attack() -> bool:
|
||||
return state == State.IDLE or state == State.WALK
|
||||
|
||||
func start_jump() -> void:
|
||||
state = State.JUMP
|
||||
height_speed = jump_intensity
|
||||
|
||||
func start_attack() -> void:
|
||||
start_directional_attack(heading)
|
||||
|
||||
func start_directional_attack(direction: Vector2) -> void:
|
||||
var attack_x := -1.0 if direction.x < 0.0 else 1.0
|
||||
attack_direction = Vector2(attack_x, 0.0)
|
||||
heading = Vector2.RIGHT if attack_x > 0.0 else Vector2.LEFT
|
||||
state = State.ATTACK
|
||||
attack_time_left = attack_duration
|
||||
attack_lunge_time_left = attack_lunge_duration
|
||||
velocity = Vector2(attack_x * attack_lunge_speed, 0.0)
|
||||
1
scenes/characters/character.gd.uid
Normal file
1
scenes/characters/character.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bd7rd0s5tsaln
|
||||
31
scenes/characters/player.gd
Normal file
31
scenes/characters/player.gd
Normal file
@@ -0,0 +1,31 @@
|
||||
class_name Player
|
||||
extends Character
|
||||
|
||||
func handle_input() -> void:
|
||||
if Input.is_action_just_pressed("ui_left"):
|
||||
judge_rhythm_action("left")
|
||||
if can_attack():
|
||||
start_directional_attack(Vector2.LEFT)
|
||||
return
|
||||
if Input.is_action_just_pressed("ui_right"):
|
||||
judge_rhythm_action("right")
|
||||
if can_attack():
|
||||
start_directional_attack(Vector2.RIGHT)
|
||||
return
|
||||
if Input.is_action_just_pressed("jump"):
|
||||
judge_rhythm_action("jump")
|
||||
if can_jump():
|
||||
start_jump()
|
||||
if state == State.IDLE or state == State.WALK:
|
||||
velocity.x = 0.0
|
||||
|
||||
func set_heading() -> void:
|
||||
if velocity.x > 0.0:
|
||||
heading = Vector2.RIGHT
|
||||
elif velocity.x < 0.0:
|
||||
heading = Vector2.LEFT
|
||||
|
||||
func judge_rhythm_action(action_name: String) -> void:
|
||||
var conductor: Node = get_tree().get_first_node_in_group("rhythm_conductor")
|
||||
if conductor != null and conductor.has_method("judge_action"):
|
||||
conductor.call("judge_action", action_name)
|
||||
1
scenes/characters/player.gd.uid
Normal file
1
scenes/characters/player.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cwp1u2srtj5ko
|
||||
249
scenes/characters/player.tscn
Normal file
249
scenes/characters/player.tscn
Normal file
@@ -0,0 +1,249 @@
|
||||
[gd_scene load_steps=10 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/characters/player.gd" id="1_player_script"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/characters/jump.png" id="2_jump_texture"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/characters/katana_attack_sheathe.png" id="3_slash_texture"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/characters/player_idle.png" id="4_idle_texture"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_player"]
|
||||
size = Vector2(16, 36)
|
||||
|
||||
[sub_resource type="Animation" id="Animation_idle"]
|
||||
resource_name = "idle"
|
||||
length = 1.0
|
||||
loop_mode = 1
|
||||
step = 0.1
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("CharacterSprite:texture")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [ExtResource("4_idle_texture")]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("CharacterSprite:hframes")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [10]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("CharacterSprite:vframes")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [1]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("CharacterSprite:offset")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [Vector2(-24, -40)]
|
||||
}
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("CharacterSprite:frame")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
|
||||
"update": 1,
|
||||
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_jump"]
|
||||
resource_name = "jump"
|
||||
length = 0.36
|
||||
loop_mode = 1
|
||||
step = 0.06
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("CharacterSprite:texture")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [ExtResource("2_jump_texture")]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("CharacterSprite:hframes")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [6]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("CharacterSprite:vframes")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [1]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("CharacterSprite:offset")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [Vector2(-24, -44)]
|
||||
}
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("CharacterSprite:frame")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
"times": PackedFloat32Array(0, 0.06, 0.12, 0.18, 0.24, 0.3),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
|
||||
"update": 1,
|
||||
"values": [0, 1, 2, 3, 4, 5]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_slash"]
|
||||
resource_name = "挥砍"
|
||||
length = 0.4
|
||||
step = 0.04
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("CharacterSprite:texture")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [ExtResource("3_slash_texture")]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("CharacterSprite:hframes")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [10]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("CharacterSprite:vframes")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [1]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("CharacterSprite:offset")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [Vector2(-40, -48)]
|
||||
}
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("CharacterSprite:frame")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
"times": PackedFloat32Array(0, 0.04, 0.08, 0.12, 0.16, 0.2, 0.24, 0.28, 0.32, 0.36),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
|
||||
"update": 1,
|
||||
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_player"]
|
||||
_data = {
|
||||
"idle": SubResource("Animation_idle"),
|
||||
"jump": SubResource("Animation_jump"),
|
||||
"挥砍": SubResource("Animation_slash")
|
||||
}
|
||||
|
||||
[node name="Player" type="CharacterBody2D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
safe_margin = 0.001
|
||||
floor_snap_length = 0.0
|
||||
scale = Vector2(4, 4)
|
||||
script = ExtResource("1_player_script")
|
||||
speed = 180.0
|
||||
jump_intensity = 304.056
|
||||
attack_duration = 0.4
|
||||
attack_lunge_duration = 0.18
|
||||
attack_lunge_speed = 220.0
|
||||
|
||||
[node name="CharacterSprite" type="Sprite2D" parent="."]
|
||||
texture = ExtResource("4_idle_texture")
|
||||
centered = false
|
||||
offset = Vector2(-24, -40)
|
||||
hframes = 10
|
||||
vframes = 1
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
position = Vector2(0, -18)
|
||||
shape = SubResource("RectangleShape2D_player")
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_player")
|
||||
}
|
||||
autoplay = "idle"
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
position = Vector2(0, -37.5)
|
||||
scale = Vector2(0.25, 0.25)
|
||||
zoom = Vector2(1.5, 1.5)
|
||||
enabled = true
|
||||
BIN
scenes/ground/.DS_Store
vendored
Normal file
BIN
scenes/ground/.DS_Store
vendored
Normal file
Binary file not shown.
46
scenes/ground/ground.tscn
Normal file
46
scenes/ground/ground.tscn
Normal file
@@ -0,0 +1,46 @@
|
||||
[gd_scene format=3 uid="uid://cs0rhloanh2u4"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://df1i0jo40oefn" path="res://assets/art/ground/ground.png" id="1_au3k8"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_au3k8"]
|
||||
size = Vector2(4105, 8)
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_rrkwn"]
|
||||
resource_name = "RectangleShape2D_left_boundary"
|
||||
size = Vector2(32, 900)
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_xmv3o"]
|
||||
resource_name = "RectangleShape2D_right_boundary"
|
||||
size = Vector2(32, 900)
|
||||
|
||||
[node name="ground" type="Node2D" unique_id=656914049]
|
||||
|
||||
[node name="GroundAnchor" type="Marker2D" parent="." unique_id=1875450741]
|
||||
position = Vector2(2047, 366)
|
||||
|
||||
[node name="GroundBody" type="StaticBody2D" parent="." unique_id=1583776637]
|
||||
position = Vector2(2047, 366)
|
||||
collision_mask = 0
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="GroundBody" unique_id=376657383]
|
||||
texture = ExtResource("1_au3k8")
|
||||
centered = false
|
||||
offset = Vector2(-2052.5, -64)
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="GroundBody" unique_id=629550857]
|
||||
position = Vector2(0, 4)
|
||||
shape = SubResource("RectangleShape2D_au3k8")
|
||||
|
||||
[node name="LeftBoundaryBody" type="StaticBody2D" parent="." unique_id=1153049543]
|
||||
position = Vector2(-21.5, -80)
|
||||
collision_mask = 0
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="LeftBoundaryBody" unique_id=595374678]
|
||||
shape = SubResource("RectangleShape2D_rrkwn")
|
||||
|
||||
[node name="RightBoundaryBody" type="StaticBody2D" parent="." unique_id=1425356485]
|
||||
position = Vector2(4115.5, -80)
|
||||
collision_mask = 0
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RightBoundaryBody" unique_id=2009094553]
|
||||
shape = SubResource("RectangleShape2D_xmv3o")
|
||||
104
scenes/main/main.gd
Normal file
104
scenes/main/main.gd
Normal file
@@ -0,0 +1,104 @@
|
||||
extends Node2D
|
||||
|
||||
@onready var rhythm_conductor: Node = $RhythmConductor
|
||||
@onready var rhythm_track: Control = $RhythmFeedback/RhythmTrack
|
||||
@onready var rhythm_feedback_label: Label = $RhythmFeedback/JudgementLabel
|
||||
@onready var center_base: TextureRect = $RhythmFeedback/RhythmTrack/CenterBase
|
||||
@onready var center_flash: TextureRect = $RhythmFeedback/RhythmTrack/CenterFlash
|
||||
@onready var left_mover: TextureRect = $RhythmFeedback/RhythmTrack/LeftMover
|
||||
@onready var right_mover: TextureRect = $RhythmFeedback/RhythmTrack/RightMover
|
||||
|
||||
var track_center := Vector2.ZERO
|
||||
var left_mover_start := Vector2.ZERO
|
||||
var right_mover_start := Vector2.ZERO
|
||||
var mover_size := Vector2.ZERO
|
||||
var center_flash_size := Vector2.ZERO
|
||||
var feedback_flash := 0.0
|
||||
var beat_flash := 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_cache_rhythm_track_layout()
|
||||
rhythm_conductor.action_judged.connect(_on_rhythm_action_judged)
|
||||
rhythm_conductor.beat.connect(_on_rhythm_beat)
|
||||
rhythm_feedback_label.text = "READY"
|
||||
_update_rhythm_track(0.0)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_update_rhythm_track(delta)
|
||||
if feedback_flash > 0.0:
|
||||
feedback_flash = maxf(0.0, feedback_flash - delta * 4.0)
|
||||
rhythm_feedback_label.scale = Vector2.ONE * (1.0 + feedback_flash * 0.18)
|
||||
|
||||
|
||||
func _on_rhythm_action_judged(action_name: String, rating: Dictionary) -> void:
|
||||
var rating_name: String = str(rating.get("label", "miss"))
|
||||
var color: Color = rating.get("color", Color("ff0055")) as Color
|
||||
var diff: float = float(rating.get("diff", INF))
|
||||
|
||||
rhythm_feedback_label.text = "%s %s %s" % [
|
||||
_format_action_name(action_name),
|
||||
rating_name.to_upper(),
|
||||
_format_signed_ms(diff),
|
||||
]
|
||||
rhythm_feedback_label.modulate = color
|
||||
feedback_flash = 1.0
|
||||
|
||||
|
||||
func _on_rhythm_beat(_position: int) -> void:
|
||||
beat_flash = 1.0
|
||||
|
||||
|
||||
func _update_rhythm_track(delta: float) -> void:
|
||||
beat_flash = maxf(0.0, beat_flash - delta * 8.0)
|
||||
var progress := 0.0
|
||||
if rhythm_conductor.has_method("get_current_beat_progress"):
|
||||
progress = float(rhythm_conductor.call("get_current_beat_progress"))
|
||||
if beat_flash > 0.15:
|
||||
progress = 1.0
|
||||
|
||||
_set_control_center(left_mover, left_mover_start.lerp(track_center, progress), mover_size)
|
||||
_set_control_center(right_mover, right_mover_start.lerp(track_center, progress), mover_size)
|
||||
_set_control_center(center_flash, track_center, center_flash_size)
|
||||
center_flash.modulate = Color(1.0, 1.0, 1.0, beat_flash)
|
||||
|
||||
|
||||
func _cache_rhythm_track_layout() -> void:
|
||||
track_center = _control_center(center_base)
|
||||
left_mover_start = _control_center(left_mover)
|
||||
right_mover_start = _control_center(right_mover)
|
||||
mover_size = left_mover.size
|
||||
center_flash_size = center_flash.size
|
||||
|
||||
|
||||
func _control_center(control: Control) -> Vector2:
|
||||
return Vector2(
|
||||
(control.offset_left + control.offset_right) * 0.5,
|
||||
(control.offset_top + control.offset_bottom) * 0.5
|
||||
)
|
||||
|
||||
|
||||
func _set_control_center(control: Control, center: Vector2, size: Vector2) -> void:
|
||||
control.offset_left = center.x - size.x * 0.5
|
||||
control.offset_top = center.y - size.y * 0.5
|
||||
control.offset_right = center.x + size.x * 0.5
|
||||
control.offset_bottom = center.y + size.y * 0.5
|
||||
|
||||
|
||||
func _format_action_name(action_name: String) -> String:
|
||||
match action_name:
|
||||
"left":
|
||||
return "LEFT"
|
||||
"right":
|
||||
return "RIGHT"
|
||||
"jump":
|
||||
return "JUMP"
|
||||
_:
|
||||
return action_name.to_upper()
|
||||
|
||||
|
||||
func _format_signed_ms(seconds: float) -> String:
|
||||
if is_inf(seconds):
|
||||
return "-- ms"
|
||||
return "%+.0f ms" % (seconds * 1000.0)
|
||||
1
scenes/main/main.gd.uid
Normal file
1
scenes/main/main.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://3n4nkejauoim
|
||||
192
scenes/main/main.tscn
Normal file
192
scenes/main/main.tscn
Normal file
@@ -0,0 +1,192 @@
|
||||
[gd_scene format=3 uid="uid://brx0c2va3831p"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://cs0rhloanh2u4" path="res://scenes/ground/ground.tscn" id="1_ground"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/characters/player.tscn" id="2_player"]
|
||||
[ext_resource type="Script" uid="uid://3n4nkejauoim" path="res://scenes/main/main.gd" id="3_main_script"]
|
||||
[ext_resource type="Script" uid="uid://brh83qp8flq5u" path="res://scenes/rhythm/rhythm_conductor.gd" id="4_rhythm_script"]
|
||||
[ext_resource type="AudioStream" uid="uid://di5ceecn088rk" path="res://assets/audio/song.ogg" id="5_song"]
|
||||
[ext_resource type="Texture2D" uid="uid://brqr1gyyxth8p" path="res://assets/ui/rhythm/center.png" id="6_center"]
|
||||
[ext_resource type="Texture2D" uid="uid://bkqec7mh5yfrd" path="res://assets/ui/rhythm/center_flash.png" id="7_center_flash"]
|
||||
[ext_resource type="Texture2D" uid="uid://cj5pa4c3arevn" path="res://assets/ui/rhythm/rod.png" id="8_rod"]
|
||||
[ext_resource type="Texture2D" uid="uid://dbmdivnpjf48l" path="res://assets/ui/rhythm/blue_ball.png" id="9_blue_ball"]
|
||||
[ext_resource type="Texture2D" uid="uid://ewr8k3lwpcna" path="res://assets/ui/rhythm/yellow_ball.png" id="10_yellow_ball"]
|
||||
|
||||
[node name="Main" type="Node2D" unique_id=596674982]
|
||||
script = ExtResource("3_main_script")
|
||||
|
||||
[node name="RhythmConductor" type="AudioStreamPlayer" parent="." unique_id=147408036]
|
||||
stream = ExtResource("5_song")
|
||||
volume_db = -10.0
|
||||
script = ExtResource("4_rhythm_script")
|
||||
|
||||
[node name="ground" parent="." unique_id=235100600 instance=ExtResource("1_ground")]
|
||||
|
||||
[node name="Player" parent="." unique_id=1027194058 instance=ExtResource("2_player")]
|
||||
position = Vector2(2047, 370)
|
||||
|
||||
[node name="RhythmFeedback" type="CanvasLayer" parent="." unique_id=979375765]
|
||||
|
||||
[node name="RhythmTrack" type="Control" parent="RhythmFeedback" unique_id=529739298]
|
||||
layout_mode = 3
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -520.0
|
||||
offset_top = 28.0
|
||||
offset_right = 520.0
|
||||
offset_bottom = 172.0
|
||||
grow_horizontal = 2
|
||||
|
||||
[node name="LeftRod" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=569576128]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 60.0
|
||||
offset_right = 464.0
|
||||
offset_bottom = 84.0
|
||||
texture = ExtResource("8_rod")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="LeftRod" type="TextureRect" parent="RhythmFeedback/RhythmTrack/LeftRod" unique_id=1074213105]
|
||||
layout_mode = 0
|
||||
offset_left = 127.0
|
||||
offset_top = 1.0
|
||||
offset_right = 527.0
|
||||
offset_bottom = 25.0
|
||||
texture = ExtResource("8_rod")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="RightRod" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=112177250]
|
||||
layout_mode = 0
|
||||
offset_left = 446.0
|
||||
offset_top = 62.0
|
||||
offset_right = 846.0
|
||||
offset_bottom = 86.0
|
||||
texture = ExtResource("8_rod")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="RightRod" type="TextureRect" parent="RhythmFeedback/RhythmTrack/RightRod" unique_id=1431511936]
|
||||
layout_mode = 0
|
||||
offset_left = 127.0
|
||||
offset_right = 527.0
|
||||
offset_bottom = 24.0
|
||||
texture = ExtResource("8_rod")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="BlueBallLeft1" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=649449082]
|
||||
layout_mode = 0
|
||||
offset_left = 184.0
|
||||
offset_top = 49.0
|
||||
offset_right = 228.0
|
||||
offset_bottom = 93.0
|
||||
texture = ExtResource("9_blue_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="BlueBallLeft2" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=1327939368]
|
||||
layout_mode = 0
|
||||
offset_left = 309.0
|
||||
offset_top = 50.0
|
||||
offset_right = 353.0
|
||||
offset_bottom = 94.0
|
||||
texture = ExtResource("9_blue_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="BlueBallLeft3" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=1352623059]
|
||||
layout_mode = 0
|
||||
offset_left = 427.0
|
||||
offset_top = 51.0
|
||||
offset_right = 471.0
|
||||
offset_bottom = 95.0
|
||||
texture = ExtResource("9_blue_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="BlueBallRight1" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=75338529]
|
||||
layout_mode = 0
|
||||
offset_left = 567.0
|
||||
offset_top = 52.0
|
||||
offset_right = 611.0
|
||||
offset_bottom = 96.0
|
||||
texture = ExtResource("9_blue_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="BlueBallRight2" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=484948530]
|
||||
layout_mode = 0
|
||||
offset_left = 687.0
|
||||
offset_top = 52.0
|
||||
offset_right = 731.0
|
||||
offset_bottom = 96.0
|
||||
texture = ExtResource("9_blue_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="BlueBallRight3" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=587810490]
|
||||
layout_mode = 0
|
||||
offset_left = 813.0
|
||||
offset_top = 52.0
|
||||
offset_right = 857.0
|
||||
offset_bottom = 96.0
|
||||
texture = ExtResource("9_blue_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="LeftMover" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=2070100389]
|
||||
layout_mode = 0
|
||||
offset_left = 183.0
|
||||
offset_top = 47.0
|
||||
offset_right = 227.0
|
||||
offset_bottom = 91.0
|
||||
texture = ExtResource("10_yellow_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="RightMover" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=1028576547]
|
||||
layout_mode = 0
|
||||
offset_left = 815.0
|
||||
offset_top = 52.0
|
||||
offset_right = 859.0
|
||||
offset_bottom = 96.0
|
||||
texture = ExtResource("10_yellow_ball")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="CenterBase" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=1816341281]
|
||||
layout_mode = 0
|
||||
offset_left = 464.0
|
||||
offset_top = 16.0
|
||||
offset_right = 576.0
|
||||
offset_bottom = 128.0
|
||||
texture = ExtResource("6_center")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="CenterFlash" type="TextureRect" parent="RhythmFeedback/RhythmTrack" unique_id=1764975684]
|
||||
modulate = Color(1, 1, 1, 0)
|
||||
layout_mode = 0
|
||||
offset_left = 440.0
|
||||
offset_top = -8.0
|
||||
offset_right = 600.0
|
||||
offset_bottom = 152.0
|
||||
texture = ExtResource("7_center_flash")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="JudgementLabel" type="Label" parent="RhythmFeedback" unique_id=776378947]
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -240.0
|
||||
offset_top = 174.0
|
||||
offset_right = 240.0
|
||||
offset_bottom = 222.0
|
||||
grow_horizontal = 2
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "READY"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
153
scenes/rhythm/rhythm_conductor.gd
Normal file
153
scenes/rhythm/rhythm_conductor.gd
Normal file
@@ -0,0 +1,153 @@
|
||||
class_name RhythmConductor
|
||||
extends AudioStreamPlayer
|
||||
|
||||
@export var bpm: float = 120.0
|
||||
@export var measures: int = 4
|
||||
@export var beat_offset: float = 0.0
|
||||
@export var perfect_window: float = 0.060
|
||||
@export var good_window: float = 0.120
|
||||
@export var bad_window: float = 0.200
|
||||
@export var starts_on_ready := true
|
||||
|
||||
var song_position := 0.0
|
||||
var song_position_in_beats := 0
|
||||
var sec_per_beat := 0.5
|
||||
var last_reported_beat := -1
|
||||
var current_measure := 1
|
||||
var running := false
|
||||
|
||||
var _start_time_usec := 0
|
||||
var _uses_fallback_clock := false
|
||||
|
||||
signal beat(position: int)
|
||||
signal measure(position: int)
|
||||
signal action_judged(action_name: String, rating: Dictionary)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("rhythm_conductor")
|
||||
sec_per_beat = 60.0 / bpm
|
||||
if starts_on_ready:
|
||||
start()
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if not running:
|
||||
return
|
||||
song_position = get_current_song_position()
|
||||
var adjusted_position: float = _apply_beat_offset(song_position)
|
||||
song_position_in_beats = int(floor(adjusted_position / sec_per_beat))
|
||||
_report_beat()
|
||||
|
||||
|
||||
func start() -> void:
|
||||
running = true
|
||||
_uses_fallback_clock = stream == null
|
||||
_start_time_usec = Time.get_ticks_usec()
|
||||
song_position = 0.0
|
||||
song_position_in_beats = 0
|
||||
last_reported_beat = -1
|
||||
current_measure = 1
|
||||
if not _uses_fallback_clock:
|
||||
play()
|
||||
|
||||
|
||||
func stop_conductor() -> void:
|
||||
if playing:
|
||||
stop()
|
||||
running = false
|
||||
|
||||
|
||||
func get_current_song_position() -> float:
|
||||
if running and playing:
|
||||
var current_position: float = get_playback_position() + AudioServer.get_time_since_last_mix()
|
||||
current_position -= AudioServer.get_output_latency()
|
||||
return maxf(0.0, current_position)
|
||||
if running:
|
||||
return float(Time.get_ticks_usec() - _start_time_usec) / 1000000.0
|
||||
return song_position
|
||||
|
||||
|
||||
func judge_action(action_name: String) -> Dictionary:
|
||||
var rating := get_current_rating()
|
||||
rating["action"] = action_name
|
||||
emit_signal("action_judged", action_name, rating)
|
||||
print("Rhythm %s: %s (%s ms)" % [
|
||||
action_name,
|
||||
str(rating.get("label", "miss")),
|
||||
_format_signed_ms(float(rating.get("diff", INF)))
|
||||
])
|
||||
return rating
|
||||
|
||||
|
||||
func get_current_rating() -> Dictionary:
|
||||
return get_rating_for_time(get_current_song_position())
|
||||
|
||||
|
||||
func get_rating_for_time(time_seconds: float) -> Dictionary:
|
||||
var adjusted_time: float = _apply_beat_offset(time_seconds)
|
||||
if adjusted_time < 0.0:
|
||||
return _rating_result("miss", Color("ff0055"), 0, 0.0, INF, INF)
|
||||
|
||||
var nearest_beat: int = int(round(adjusted_time / sec_per_beat))
|
||||
var nearest_beat_time: float = nearest_beat * sec_per_beat
|
||||
var diff: float = adjusted_time - nearest_beat_time
|
||||
var abs_diff: float = absf(diff)
|
||||
|
||||
if abs_diff <= perfect_window:
|
||||
return _rating_result("perfect", Color("00f2ff"), nearest_beat, nearest_beat_time, diff, abs_diff)
|
||||
if abs_diff <= good_window:
|
||||
return _rating_result("good", Color("ffffff"), nearest_beat, nearest_beat_time, diff, abs_diff)
|
||||
if abs_diff <= bad_window:
|
||||
return _rating_result("bad", Color("ffaa00"), nearest_beat, nearest_beat_time, diff, abs_diff)
|
||||
return _rating_result("miss", Color("ff0055"), nearest_beat, nearest_beat_time, diff, abs_diff)
|
||||
|
||||
|
||||
func get_current_beat_progress() -> float:
|
||||
return get_beat_progress_for_time(get_current_song_position())
|
||||
|
||||
|
||||
func get_beat_progress_for_time(time_seconds: float) -> float:
|
||||
var adjusted_time: float = _apply_beat_offset(time_seconds)
|
||||
if adjusted_time < 0.0:
|
||||
return 0.0
|
||||
return fposmod(adjusted_time, sec_per_beat) / sec_per_beat
|
||||
|
||||
|
||||
func get_time_to_nearest_beat(time_seconds: float) -> float:
|
||||
var adjusted_time: float = _apply_beat_offset(time_seconds)
|
||||
if adjusted_time < 0.0:
|
||||
return INF
|
||||
var nearest_beat_time: float = round(adjusted_time / sec_per_beat) * sec_per_beat
|
||||
return adjusted_time - nearest_beat_time
|
||||
|
||||
|
||||
func _report_beat() -> void:
|
||||
if last_reported_beat < song_position_in_beats:
|
||||
if current_measure > measures:
|
||||
current_measure = 1
|
||||
emit_signal("beat", song_position_in_beats)
|
||||
emit_signal("measure", current_measure)
|
||||
last_reported_beat = song_position_in_beats
|
||||
current_measure += 1
|
||||
|
||||
|
||||
func _apply_beat_offset(time_seconds: float) -> float:
|
||||
return time_seconds + beat_offset
|
||||
|
||||
|
||||
func _rating_result(label: String, color: Color, nearest_beat: int, nearest_beat_time: float, diff: float, abs_diff: float) -> Dictionary:
|
||||
return {
|
||||
"label": label,
|
||||
"color": color,
|
||||
"nearest_beat": nearest_beat,
|
||||
"nearest_beat_time": nearest_beat_time,
|
||||
"diff": diff,
|
||||
"abs_diff": abs_diff,
|
||||
}
|
||||
|
||||
|
||||
func _format_signed_ms(seconds: float) -> String:
|
||||
if is_inf(seconds):
|
||||
return "--"
|
||||
return "%+.0f" % (seconds * 1000.0)
|
||||
1
scenes/rhythm/rhythm_conductor.gd.uid
Normal file
1
scenes/rhythm/rhythm_conductor.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://brh83qp8flq5u
|
||||
Reference in New Issue
Block a user