reorganization
126
source/ExplodingLine.tscn
Normal file
@@ -0,0 +1,126 @@
|
||||
[gd_scene load_steps=10 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/Mino/MinoMaterial.tres" type="Material" id=1]
|
||||
|
||||
[sub_resource type="Gradient" id=1]
|
||||
|
||||
offsets = PoolRealArray( 0, 1 )
|
||||
colors = PoolColorArray( 0, 0, 0, 1, 1, 1, 1, 1 )
|
||||
|
||||
[sub_resource type="GradientTexture" id=2]
|
||||
|
||||
flags = 4
|
||||
gradient = SubResource( 1 )
|
||||
width = 2048
|
||||
|
||||
[sub_resource type="Gradient" id=3]
|
||||
|
||||
offsets = PoolRealArray( 0, 1 )
|
||||
colors = PoolColorArray( 0.802765, 1, 0.451172, 1, 1, 1, 1, 0 )
|
||||
|
||||
[sub_resource type="GradientTexture" id=4]
|
||||
|
||||
flags = 4
|
||||
gradient = SubResource( 3 )
|
||||
width = 2048
|
||||
|
||||
[sub_resource type="ParticlesMaterial" id=5]
|
||||
|
||||
render_priority = 0
|
||||
trail_divisor = 2
|
||||
trail_color_modifier = SubResource( 4 )
|
||||
emission_shape = 2
|
||||
emission_box_extents = Vector3( 8, 0.5, 0.5 )
|
||||
flag_align_y = false
|
||||
flag_rotate_y = true
|
||||
flag_disable_z = false
|
||||
spread = 30.0
|
||||
flatness = 0.0
|
||||
gravity = Vector3( 0, -30, 0 )
|
||||
initial_velocity = 10.0
|
||||
initial_velocity_random = 0.8
|
||||
angular_velocity = 97.14
|
||||
angular_velocity_random = 1.0
|
||||
linear_accel = 100.0
|
||||
linear_accel_random = 0.84
|
||||
radial_accel = 8.48
|
||||
radial_accel_random = 0.85
|
||||
tangential_accel = 0.0
|
||||
tangential_accel_random = 0.0
|
||||
damping = 0.0
|
||||
damping_random = 0.0
|
||||
angle = 402.7
|
||||
angle_random = 0.78
|
||||
scale = 0.5
|
||||
scale_random = 1.0
|
||||
color_ramp = SubResource( 2 )
|
||||
hue_variation = 0.0
|
||||
hue_variation_random = 0.0
|
||||
anim_speed = 0.0
|
||||
anim_speed_random = 0.0
|
||||
anim_offset = 0.0
|
||||
anim_offset_random = 0.0
|
||||
anim_loop = false
|
||||
_sections_unfolded = [ "Angular Velocity", "Color", "Emission Shape", "Gravity", "Scale", "Spread" ]
|
||||
|
||||
[sub_resource type="PrismMesh" id=6]
|
||||
|
||||
material = ExtResource( 1 )
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
left_to_right = 2.98023e-008
|
||||
size = Vector3( 0.2, 0.2, 0.2 )
|
||||
subdivide_width = 2
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
[sub_resource type="PrismMesh" id=7]
|
||||
|
||||
material = ExtResource( 1 )
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
left_to_right = 0.5
|
||||
size = Vector3( 0.2, 0.2, 0.2 )
|
||||
subdivide_width = 2
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
[sub_resource type="CubeMesh" id=8]
|
||||
|
||||
material = ExtResource( 1 )
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
size = Vector3( 0.2, 0.2, 0.2 )
|
||||
subdivide_width = 0
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
[node name="ExplodingLine" type="Particles"]
|
||||
|
||||
layers = 1
|
||||
material_override = null
|
||||
cast_shadow = 1
|
||||
extra_cull_margin = 0.0
|
||||
use_in_baked_light = false
|
||||
lod_min_distance = 0.0
|
||||
lod_min_hysteresis = 0.0
|
||||
lod_max_distance = 0.0
|
||||
lod_max_hysteresis = 0.0
|
||||
emitting = false
|
||||
amount = 800
|
||||
lifetime = 2.0
|
||||
one_shot = true
|
||||
preprocess = 0.0
|
||||
speed_scale = 2.18
|
||||
explosiveness = 0.9
|
||||
randomness = 0.69
|
||||
fixed_fps = 0
|
||||
fract_delta = true
|
||||
visibility_aabb = AABB( -5, -0.5, -1, 10, 1, 2 )
|
||||
local_coords = false
|
||||
draw_order = 0
|
||||
process_material = SubResource( 5 )
|
||||
draw_passes = 3
|
||||
draw_pass_1 = SubResource( 6 )
|
||||
draw_pass_2 = SubResource( 7 )
|
||||
draw_pass_3 = SubResource( 8 )
|
||||
_sections_unfolded = [ "Draw Passes", "Drawing", "Process Material", "Time", "Transform" ]
|
||||
|
||||
|
||||
16
source/FlashText.gd
Normal file
@@ -0,0 +1,16 @@
|
||||
extends Control
|
||||
|
||||
var texts = PoolStringArray()
|
||||
|
||||
func print(text):
|
||||
texts.append(text)
|
||||
if texts.size() > 4:
|
||||
texts.remove(0)
|
||||
$Label.text = texts.join("\n")
|
||||
$AnimationPlayer.play("Flash")
|
||||
|
||||
func _on_AnimationPlayer_animation_finished(anim_name):
|
||||
texts.resize(0)
|
||||
|
||||
func _on_Stats_flash_text(text):
|
||||
self.print(text)
|
||||
62
source/GridMap.gd
Normal file
@@ -0,0 +1,62 @@
|
||||
extends GridMap
|
||||
|
||||
const Tetromino = preload("res://Tetrominos/Tetromino.gd")
|
||||
const ExplodingLine = preload("res://ExplodingLine.tscn")
|
||||
|
||||
const EMPTY_CELL = -1
|
||||
const MINO = 0
|
||||
|
||||
export (int) var NB_LINES = 20
|
||||
export (int) var NB_COLLUMNS = 10
|
||||
|
||||
var exploding_lines = []
|
||||
|
||||
func _ready():
|
||||
for y in range(NB_LINES):
|
||||
exploding_lines.append(ExplodingLine.instance())
|
||||
add_child(exploding_lines[y])
|
||||
exploding_lines[y].translation = Vector3(NB_COLLUMNS/2, y, 1)
|
||||
|
||||
func clear():
|
||||
for position in get_used_cells():
|
||||
set_cell_item(position.x, position.y, position.z, EMPTY_CELL)
|
||||
|
||||
func is_free_cell(position):
|
||||
return (
|
||||
0 <= position.x and position.x < NB_COLLUMNS
|
||||
and position.y >= 0
|
||||
and get_cell_item(position.x, position.y, 0) == GridMap.INVALID_CELL_ITEM
|
||||
)
|
||||
|
||||
func possible_positions(initial_positions, movement):
|
||||
var position
|
||||
var test_positions = []
|
||||
for i in range(4):
|
||||
position = initial_positions[i] + movement
|
||||
if is_free_cell(position):
|
||||
test_positions.append(position)
|
||||
if test_positions.size() == Tetromino.NB_MINOES:
|
||||
return test_positions
|
||||
else:
|
||||
return []
|
||||
|
||||
func lock(piece):
|
||||
for position in piece.positions():
|
||||
set_cell_item(position.x, position.y, 0, MINO)
|
||||
|
||||
func clear_lines():
|
||||
var line_cleared
|
||||
var lines_cleared = 0
|
||||
for y in range(NB_LINES-1, -1, -1):
|
||||
line_cleared = true
|
||||
for x in range(NB_COLLUMNS):
|
||||
if not get_cell_item(x, y, 0) == MINO:
|
||||
line_cleared = false
|
||||
break
|
||||
if line_cleared:
|
||||
for y2 in range(y, NB_LINES+2):
|
||||
for x in range(NB_COLLUMNS):
|
||||
set_cell_item(x, y2, 0, get_cell_item(x, y2+1, 0))
|
||||
lines_cleared += 1
|
||||
exploding_lines[y].restart()
|
||||
return lines_cleared
|
||||
245
source/Main.gd
Normal file
@@ -0,0 +1,245 @@
|
||||
extends WorldEnvironment
|
||||
|
||||
const Tetromino = preload("res://Tetrominos/Tetromino.gd")
|
||||
const TetroI = preload("res://Tetrominos/TetroI.tscn")
|
||||
const TetroJ = preload("res://Tetrominos/TetroJ.tscn")
|
||||
const TetroL = preload("res://Tetrominos/TetroL.tscn")
|
||||
const TetroO = preload("res://Tetrominos/TetroO.tscn")
|
||||
const TetroS = preload("res://Tetrominos/TetroS.tscn")
|
||||
const TetroT = preload("res://Tetrominos/TetroT.tscn")
|
||||
const TetroZ = preload("res://Tetrominos/TetroZ.tscn")
|
||||
|
||||
const password = "TETRIS 3000"
|
||||
|
||||
const NEXT_POSITION = Vector3(13, 16, 0)
|
||||
const START_POSITION = Vector3(5, 20, 0)
|
||||
const HOLD_POSITION = Vector3(-5, 16, 0)
|
||||
|
||||
const movements = {
|
||||
"move_right": Vector3(1, 0, 0),
|
||||
"move_left": Vector3(-1, 0, 0),
|
||||
"soft_drop": Vector3(0, -1, 0)
|
||||
}
|
||||
|
||||
var random_bag = []
|
||||
|
||||
var next_piece
|
||||
var current_piece
|
||||
var held_piece
|
||||
var current_piece_held
|
||||
|
||||
var autoshift_action = ""
|
||||
|
||||
var playing = false
|
||||
|
||||
signal piece_dropped(score)
|
||||
signal piece_locked(lines, t_spin)
|
||||
|
||||
func _ready():
|
||||
load_user_data()
|
||||
|
||||
func load_user_data():
|
||||
var save_game = File.new()
|
||||
if not save_game.file_exists("user://data.save"):
|
||||
$Stats.high_score = 0
|
||||
else:
|
||||
save_game.open_encrypted_with_pass("user://data.save", File.READ, password)
|
||||
$Stats.high_score = int(save_game.get_line())
|
||||
$Stats/VBC/HighScore.text = str($Stats.high_score)
|
||||
save_game.close()
|
||||
|
||||
func _on_Start_start(level):
|
||||
$GridMap.clear()
|
||||
if held_piece:
|
||||
remove_child(held_piece)
|
||||
held_piece = null
|
||||
current_piece_held = false
|
||||
next_piece = random_piece()
|
||||
new_piece()
|
||||
$MidiPlayer.position = 0
|
||||
$Start.visible = false
|
||||
$Stats.new_game(level)
|
||||
resume()
|
||||
|
||||
func new_piece():
|
||||
if current_piece:
|
||||
remove_child(current_piece)
|
||||
current_piece = next_piece
|
||||
current_piece.translation = START_POSITION
|
||||
current_piece.emit_trail(true)
|
||||
autoshift_action = ""
|
||||
next_piece = random_piece()
|
||||
next_piece.translation = NEXT_POSITION
|
||||
if move(movements["soft_drop"]):
|
||||
$DropTimer.start()
|
||||
$LockDelay.start()
|
||||
current_piece_held = false
|
||||
else:
|
||||
current_piece.translate(movements["soft_drop"])
|
||||
game_over()
|
||||
|
||||
func random_piece():
|
||||
if not random_bag:
|
||||
random_bag = [
|
||||
TetroI, TetroJ, TetroL, TetroO,
|
||||
TetroS, TetroT, TetroZ
|
||||
]
|
||||
var choice = randi() % random_bag.size()
|
||||
var piece = random_bag[choice].instance()
|
||||
random_bag.remove(choice)
|
||||
add_child(piece)
|
||||
return piece
|
||||
|
||||
func _on_Stats_level_up():
|
||||
$DropTimer.wait_time = pow(0.8 - (($Stats.level - 1) * 0.007), $Stats.level - 1)
|
||||
if $Stats.level > 15:
|
||||
$LockDelay.wait_time = 0.5 * pow(0.9, $Stats.level-15)
|
||||
|
||||
func _process(delta):
|
||||
if Input.is_action_just_pressed("pause"):
|
||||
if playing:
|
||||
pause()
|
||||
$controls_ui.visible = true
|
||||
elif $controls_ui.enable_resume:
|
||||
resume()
|
||||
if Input.is_action_just_pressed("toggle_fullscreen"):
|
||||
OS.window_fullscreen = !OS.window_fullscreen
|
||||
if playing:
|
||||
for action in movements:
|
||||
if action == autoshift_action:
|
||||
if not Input.is_action_pressed(action):
|
||||
$AutoShiftDelay.stop()
|
||||
$AutoShiftTimer.stop()
|
||||
autoshift_action = ""
|
||||
else:
|
||||
if Input.is_action_pressed(action):
|
||||
autoshift_action = action
|
||||
process_autoshift_action()
|
||||
$AutoShiftTimer.stop()
|
||||
$AutoShiftDelay.start()
|
||||
if Input.is_action_just_pressed("hard_drop"):
|
||||
hard_drop()
|
||||
if Input.is_action_just_pressed("rotate_clockwise"):
|
||||
rotate(Tetromino.CLOCKWISE)
|
||||
if Input.is_action_just_pressed("rotate_counterclockwise"):
|
||||
rotate(Tetromino.COUNTERCLOCKWISE)
|
||||
if Input.is_action_just_pressed("hold"):
|
||||
hold()
|
||||
|
||||
func _on_AutoShiftDelay_timeout():
|
||||
if playing and autoshift_action:
|
||||
process_autoshift_action()
|
||||
$AutoShiftTimer.start()
|
||||
|
||||
func _on_AutoShiftTimer_timeout():
|
||||
if playing and autoshift_action:
|
||||
process_autoshift_action()
|
||||
|
||||
func process_autoshift_action():
|
||||
if move(movements[autoshift_action]):
|
||||
if autoshift_action == "soft_drop":
|
||||
emit_signal("piece_dropped", 1)
|
||||
|
||||
func hard_drop():
|
||||
var score = 0
|
||||
while move(movements["soft_drop"]):
|
||||
score += 2
|
||||
emit_signal("piece_dropped", score)
|
||||
lock()
|
||||
|
||||
func move(movement):
|
||||
if current_piece.move(movement):
|
||||
$LockDelay.start()
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func rotate(direction):
|
||||
if current_piece.rotate(direction):
|
||||
$LockDelay.start()
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func _on_DropTimer_timeout():
|
||||
move(movements["soft_drop"])
|
||||
|
||||
func _on_LockDelay_timeout():
|
||||
if not move(movements["soft_drop"]):
|
||||
lock()
|
||||
|
||||
func lock():
|
||||
$GridMap.lock(current_piece)
|
||||
emit_signal("piece_locked", $GridMap.clear_lines(), current_piece.t_spin)
|
||||
new_piece()
|
||||
|
||||
func hold():
|
||||
if not current_piece_held:
|
||||
current_piece_held = true
|
||||
var swap = current_piece
|
||||
current_piece = held_piece
|
||||
held_piece = swap
|
||||
held_piece.emit_trail(false)
|
||||
held_piece.translation = HOLD_POSITION
|
||||
if current_piece:
|
||||
current_piece.translation = START_POSITION
|
||||
current_piece.emit_trail(true)
|
||||
else:
|
||||
new_piece()
|
||||
|
||||
func resume():
|
||||
playing = true
|
||||
$DropTimer.start()
|
||||
$LockDelay.start()
|
||||
$Stats.time = OS.get_system_time_secs() - $Stats.time
|
||||
$Stats/Clock.start()
|
||||
$MidiPlayer.resume()
|
||||
$controls_ui.visible = false
|
||||
$Stats.visible = true
|
||||
$GridMap.visible = true
|
||||
$Backs.visible = true
|
||||
current_piece.visible = true
|
||||
if held_piece:
|
||||
held_piece.visible = true
|
||||
next_piece.visible = true
|
||||
|
||||
func pause(hide=true):
|
||||
playing = false
|
||||
$Stats.time = OS.get_system_time_secs() - $Stats.time
|
||||
if hide:
|
||||
$Stats.visible = false
|
||||
$GridMap.visible = false
|
||||
$Backs.visible = false
|
||||
current_piece.visible = false
|
||||
if held_piece:
|
||||
held_piece.visible = false
|
||||
next_piece.visible = false
|
||||
$MidiPlayer.stop()
|
||||
$DropTimer.stop()
|
||||
$LockDelay.stop()
|
||||
$Stats/Clock.stop()
|
||||
|
||||
func game_over():
|
||||
$FlashText.print("GAME\nOVER")
|
||||
pause(false)
|
||||
$ReplayButton.visible = true
|
||||
|
||||
func _on_ReplayButton_pressed():
|
||||
pause()
|
||||
$ReplayButton.visible = false
|
||||
$Start.visible = true
|
||||
|
||||
func _notification(what):
|
||||
match what:
|
||||
MainLoop.NOTIFICATION_WM_FOCUS_OUT:
|
||||
if playing:
|
||||
pause()
|
||||
MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
|
||||
save_user_data()
|
||||
get_tree().quit()
|
||||
|
||||
func save_user_data():
|
||||
var save_game = File.new()
|
||||
save_game.open_encrypted_with_pass("user://data.save", File.WRITE, password)
|
||||
save_game.store_line(str($Stats.high_score))
|
||||
save_game.close()
|
||||
557
source/Main.tscn
Normal file
@@ -0,0 +1,557 @@
|
||||
[gd_scene load_steps=21 format=2]
|
||||
|
||||
[ext_resource path="res://Main.gd" type="Script" id=1]
|
||||
[ext_resource path="res://night-sky-background-14391263141jp.jpg" type="Texture" id=2]
|
||||
[ext_resource path="res://Tetrominos/Mino/MinoLibrary.tres" type="MeshLibrary" id=3]
|
||||
[ext_resource path="res://GridMap.gd" type="Script" id=4]
|
||||
[ext_resource path="res://midi/MidiPlayer.tscn" type="PackedScene" id=5]
|
||||
[ext_resource path="res://MidiPlayer.gd" type="Script" id=6]
|
||||
[ext_resource path="res://FlashText.gd" type="Script" id=7]
|
||||
[ext_resource path="res://fonts/525-ROUN.TTF" type="DynamicFontData" id=8]
|
||||
[ext_resource path="res://Stats.tscn" type="PackedScene" id=9]
|
||||
[ext_resource path="res://controls.tscn" type="PackedScene" id=10]
|
||||
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=11]
|
||||
[ext_resource path="res://Start.tscn" type="PackedScene" id=12]
|
||||
|
||||
[sub_resource type="PanoramaSky" id=1]
|
||||
|
||||
radiance_size = 0
|
||||
|
||||
[sub_resource type="Environment" id=2]
|
||||
|
||||
resource_local_to_scene = true
|
||||
background_mode = 0
|
||||
background_sky = SubResource( 1 )
|
||||
background_sky_custom_fov = 0.0
|
||||
background_color = Color( 0, 0, 0, 1 )
|
||||
background_energy = 0.0
|
||||
background_canvas_max_layer = 0
|
||||
ambient_light_color = Color( 0.86908, 0.949502, 0.958984, 1 )
|
||||
ambient_light_energy = 2.0
|
||||
ambient_light_sky_contribution = 0.92
|
||||
fog_enabled = false
|
||||
fog_color = Color( 0.5, 0.6, 0.7, 1 )
|
||||
fog_sun_color = Color( 1, 0.9, 0.7, 1 )
|
||||
fog_sun_amount = 0.0
|
||||
fog_depth_enabled = true
|
||||
fog_depth_begin = 10.0
|
||||
fog_depth_curve = 1.0
|
||||
fog_transmit_enabled = false
|
||||
fog_transmit_curve = 1.0
|
||||
fog_height_enabled = false
|
||||
fog_height_min = 0.0
|
||||
fog_height_max = 100.0
|
||||
fog_height_curve = 1.0
|
||||
tonemap_mode = 3
|
||||
tonemap_exposure = 1.0
|
||||
tonemap_white = 1.0
|
||||
auto_exposure_enabled = false
|
||||
auto_exposure_scale = 0.4
|
||||
auto_exposure_min_luma = 0.05
|
||||
auto_exposure_max_luma = 0.26
|
||||
auto_exposure_speed = 0.5
|
||||
ss_reflections_enabled = false
|
||||
ss_reflections_max_steps = 64
|
||||
ss_reflections_fade_in = 0.15
|
||||
ss_reflections_fade_out = 2.0
|
||||
ss_reflections_depth_tolerance = 0.2
|
||||
ss_reflections_roughness = true
|
||||
ssao_enabled = false
|
||||
ssao_radius = 1.0
|
||||
ssao_intensity = 1.0
|
||||
ssao_radius2 = 0.0
|
||||
ssao_intensity2 = 1.0
|
||||
ssao_bias = 0.01
|
||||
ssao_light_affect = 0.0
|
||||
ssao_color = Color( 0, 0, 0, 1 )
|
||||
ssao_quality = 0
|
||||
ssao_blur = 3
|
||||
ssao_edge_sharpness = 4.0
|
||||
dof_blur_far_enabled = false
|
||||
dof_blur_far_distance = 10.0
|
||||
dof_blur_far_transition = 5.0
|
||||
dof_blur_far_amount = 0.1
|
||||
dof_blur_far_quality = 1
|
||||
dof_blur_near_enabled = false
|
||||
dof_blur_near_distance = 2.0
|
||||
dof_blur_near_transition = 1.0
|
||||
dof_blur_near_amount = 0.1
|
||||
dof_blur_near_quality = 1
|
||||
glow_enabled = false
|
||||
glow_levels/1 = false
|
||||
glow_levels/2 = false
|
||||
glow_levels/3 = true
|
||||
glow_levels/4 = false
|
||||
glow_levels/5 = true
|
||||
glow_levels/6 = false
|
||||
glow_levels/7 = false
|
||||
glow_intensity = 6.17
|
||||
glow_strength = 1.0
|
||||
glow_bloom = 0.0
|
||||
glow_blend_mode = 2
|
||||
glow_hdr_threshold = 1.0
|
||||
glow_hdr_scale = 2.0
|
||||
glow_bicubic_upscale = false
|
||||
adjustment_enabled = false
|
||||
adjustment_brightness = 0.27
|
||||
adjustment_contrast = 1.0
|
||||
adjustment_saturation = 0.34
|
||||
_sections_unfolded = [ "Adjustments", "Ambient Light", "Background", "Resource" ]
|
||||
|
||||
[sub_resource type="SpatialMaterial" id=3]
|
||||
|
||||
render_priority = 0
|
||||
flags_transparent = true
|
||||
flags_unshaded = false
|
||||
flags_vertex_lighting = false
|
||||
flags_no_depth_test = false
|
||||
flags_use_point_size = false
|
||||
flags_world_triplanar = false
|
||||
flags_fixed_size = false
|
||||
flags_albedo_tex_force_srgb = false
|
||||
vertex_color_use_as_albedo = false
|
||||
vertex_color_is_srgb = false
|
||||
params_diffuse_mode = 0
|
||||
params_specular_mode = 0
|
||||
params_blend_mode = 1
|
||||
params_cull_mode = 0
|
||||
params_depth_draw_mode = 0
|
||||
params_line_width = 1.0
|
||||
params_point_size = 1.0
|
||||
params_billboard_mode = 0
|
||||
params_grow = false
|
||||
params_use_alpha_scissor = false
|
||||
albedo_color = Color( 0.601563, 0.775878, 1, 0.00784314 )
|
||||
metallic = 0.0
|
||||
metallic_specular = 0.0
|
||||
metallic_texture_channel = 0
|
||||
roughness = 0.46
|
||||
roughness_texture_channel = 0
|
||||
emission_enabled = false
|
||||
normal_enabled = false
|
||||
rim_enabled = false
|
||||
clearcoat_enabled = false
|
||||
anisotropy_enabled = false
|
||||
ao_enabled = false
|
||||
depth_enabled = false
|
||||
subsurf_scatter_enabled = false
|
||||
transmission_enabled = false
|
||||
refraction_enabled = false
|
||||
detail_enabled = false
|
||||
uv1_scale = Vector3( 1, 1, 1 )
|
||||
uv1_offset = Vector3( 0, 0, 0 )
|
||||
uv1_triplanar = false
|
||||
uv1_triplanar_sharpness = 1.0
|
||||
uv2_scale = Vector3( 1, 1, 1 )
|
||||
uv2_offset = Vector3( 0, 0, 0 )
|
||||
uv2_triplanar = false
|
||||
uv2_triplanar_sharpness = 1.0
|
||||
proximity_fade_enable = true
|
||||
proximity_fade_distance = 1.0
|
||||
distance_fade_enable = false
|
||||
_sections_unfolded = [ "Albedo", "Emission", "Metallic", "NormalMap", "Proximity Fade" ]
|
||||
|
||||
[sub_resource type="CubeMesh" id=4]
|
||||
|
||||
material = SubResource( 3 )
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
size = Vector3( 1, 1, 1 )
|
||||
subdivide_width = 0
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
[sub_resource type="CubeMesh" id=5]
|
||||
|
||||
material = SubResource( 3 )
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
size = Vector3( 1, 1, 1 )
|
||||
subdivide_width = 0
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
[sub_resource type="DynamicFont" id=6]
|
||||
|
||||
size = 30
|
||||
use_mipmaps = true
|
||||
use_filter = false
|
||||
font_data = ExtResource( 8 )
|
||||
_sections_unfolded = [ "Font", "Settings" ]
|
||||
|
||||
[sub_resource type="Animation" id=7]
|
||||
|
||||
resource_name = "Flash"
|
||||
length = 1.0
|
||||
loop = false
|
||||
step = 0.1
|
||||
tracks/0/type = "value"
|
||||
tracks/0/path = NodePath("Label:custom_fonts/font:size")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/keys = {
|
||||
"times": PoolRealArray( 0, 0.1, 0.9, 1 ),
|
||||
"transitions": PoolRealArray( 1, 0, 1, 1 ),
|
||||
"update": 0,
|
||||
"values": [ 30, 30, 30, 50 ]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/path = NodePath("Label:custom_colors/font_color")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/keys = {
|
||||
"times": PoolRealArray( 0, 0.1, 0.9, 1 ),
|
||||
"transitions": PoolRealArray( 1, 1, 1, 1 ),
|
||||
"update": 0,
|
||||
"values": [ Color( 0.445404, 0.710476, 0.820313, 0 ), Color( 0.445404, 0.710476, 0.820313, 0.533765 ), Color( 0.445404, 0.710476, 0.820313, 0.533765 ), Color( 0.445404, 0.710476, 0.820313, 0 ) ]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/path = NodePath("Label:rect_position")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/keys = {
|
||||
"times": PoolRealArray( 0, 0.1, 0.9, 1 ),
|
||||
"transitions": PoolRealArray( 1, 1, 1, 1 ),
|
||||
"update": 0,
|
||||
"values": [ Vector2( 0, 110 ), Vector2( 0, -50 ), Vector2( 0, -60 ), Vector2( 0, -100 ) ]
|
||||
}
|
||||
|
||||
[sub_resource type="DynamicFont" id=8]
|
||||
|
||||
size = 20
|
||||
use_mipmaps = false
|
||||
use_filter = false
|
||||
extra_spacing_bottom = 5
|
||||
font_data = ExtResource( 11 )
|
||||
_sections_unfolded = [ "Extra Spacing", "Font", "Settings" ]
|
||||
|
||||
[node name="Main" type="WorldEnvironment" index="0"]
|
||||
|
||||
environment = SubResource( 2 )
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Sprite3D" type="Sprite3D" parent="." index="0"]
|
||||
|
||||
transform = Transform( 19.2, 0, 0, 0, 12.8, 0, 0, 0, 1, 5, 10, -80 )
|
||||
layers = 1
|
||||
material_override = null
|
||||
cast_shadow = 1
|
||||
extra_cull_margin = 0.0
|
||||
use_in_baked_light = false
|
||||
lod_min_distance = 0.0
|
||||
lod_min_hysteresis = 0.0
|
||||
lod_max_distance = 0.0
|
||||
lod_max_hysteresis = 0.0
|
||||
centered = true
|
||||
offset = Vector2( 0, 0 )
|
||||
flip_h = false
|
||||
flip_v = false
|
||||
modulate = Color( 0.478431, 0.478431, 0.478431, 1 )
|
||||
opacity = 0.0
|
||||
pixel_size = 0.01
|
||||
axis = 2
|
||||
transparent = false
|
||||
shaded = false
|
||||
double_sided = false
|
||||
alpha_cut = 0
|
||||
texture = ExtResource( 2 )
|
||||
vframes = 1
|
||||
hframes = 1
|
||||
frame = 0
|
||||
region_enabled = false
|
||||
region_rect = Rect2( 0, 0, 0, 0 )
|
||||
_sections_unfolded = [ "Animation", "Flags", "Transform" ]
|
||||
|
||||
[node name="Camera" type="Camera" parent="." index="1"]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 4.5, 10, 20 )
|
||||
keep_aspect = 1
|
||||
cull_mask = 1048575
|
||||
environment = null
|
||||
h_offset = 0.0
|
||||
v_offset = 0.0
|
||||
doppler_tracking = 0
|
||||
projection = 0
|
||||
current = true
|
||||
fov = 70.0
|
||||
size = 1.0
|
||||
near = 0.1
|
||||
far = 1.29056e+006
|
||||
_sections_unfolded = [ "Transform" ]
|
||||
|
||||
[node name="DirectionalLight" type="DirectionalLight" parent="." index="2"]
|
||||
|
||||
transform = Transform( 0.332668, 0.771982, -0.541642, 0.579657, 0.285656, 0.763151, 0.743861, -0.567843, -0.352456, 5, 30, 0 )
|
||||
layers = 1
|
||||
light_color = Color( 1, 1, 1, 1 )
|
||||
light_energy = 3.0
|
||||
light_indirect_energy = 1.0
|
||||
light_negative = false
|
||||
light_specular = 0.5
|
||||
light_bake_mode = 1
|
||||
light_cull_mask = -1
|
||||
shadow_enabled = false
|
||||
shadow_color = Color( 0, 0, 0, 1 )
|
||||
shadow_bias = 0.1
|
||||
shadow_contact = 0.0
|
||||
shadow_reverse_cull_face = false
|
||||
editor_only = false
|
||||
directional_shadow_mode = 2
|
||||
directional_shadow_split_1 = 0.1
|
||||
directional_shadow_split_2 = 0.2
|
||||
directional_shadow_split_3 = 0.5
|
||||
directional_shadow_blend_splits = false
|
||||
directional_shadow_normal_bias = 0.8
|
||||
directional_shadow_bias_split_scale = 0.25
|
||||
directional_shadow_depth_range = 0
|
||||
directional_shadow_max_distance = 200.0
|
||||
_sections_unfolded = [ "Light", "Transform" ]
|
||||
|
||||
[node name="GridMap" type="GridMap" parent="." index="3"]
|
||||
|
||||
theme = ExtResource( 3 )
|
||||
cell_size = Vector3( 1, 1, 1 )
|
||||
cell_octant_size = 8
|
||||
cell_center_x = false
|
||||
cell_center_y = false
|
||||
cell_center_z = false
|
||||
cell_scale = 1.0
|
||||
collision_layer = 1
|
||||
collision_mask = 1
|
||||
data = {
|
||||
"cells": PoolIntArray( )
|
||||
}
|
||||
script = ExtResource( 4 )
|
||||
_sections_unfolded = [ "Cell", "Transform" ]
|
||||
__meta__ = {
|
||||
"_editor_clip_": 1,
|
||||
"_editor_floor_": Vector3( 0, -1, 0 )
|
||||
}
|
||||
NB_LINES = 20
|
||||
NB_COLLUMNS = 10
|
||||
|
||||
[node name="Backs" type="Spatial" parent="." index="4"]
|
||||
|
||||
editor/display_folded = true
|
||||
visible = false
|
||||
|
||||
[node name="GridBack" type="MeshInstance" parent="Backs" index="0"]
|
||||
|
||||
transform = Transform( 10, 0, 0, 0, 20, 0, 0, 0, 1, 4.5, 9.5, 0 )
|
||||
layers = 1
|
||||
material_override = null
|
||||
cast_shadow = 1
|
||||
extra_cull_margin = 0.0
|
||||
use_in_baked_light = false
|
||||
lod_min_distance = 0.0
|
||||
lod_min_hysteresis = 0.0
|
||||
lod_max_distance = 0.0
|
||||
lod_max_hysteresis = 0.0
|
||||
mesh = SubResource( 4 )
|
||||
skeleton = NodePath("..")
|
||||
material/0 = null
|
||||
_sections_unfolded = [ "Transform" ]
|
||||
|
||||
[node name="HoldBack" type="MeshInstance" parent="Backs" index="1"]
|
||||
|
||||
transform = Transform( 7, 0, 0, 0, 7, 0, 0, 0, 1, -5, 16, 0 )
|
||||
layers = 1
|
||||
material_override = null
|
||||
cast_shadow = 1
|
||||
extra_cull_margin = 0.0
|
||||
use_in_baked_light = false
|
||||
lod_min_distance = 0.0
|
||||
lod_min_hysteresis = 0.0
|
||||
lod_max_distance = 0.0
|
||||
lod_max_hysteresis = 0.0
|
||||
mesh = SubResource( 5 )
|
||||
skeleton = NodePath("..")
|
||||
material/0 = null
|
||||
_sections_unfolded = [ "Transform" ]
|
||||
|
||||
[node name="NextBack" type="MeshInstance" parent="Backs" index="2"]
|
||||
|
||||
transform = Transform( 7, 0, 0, 0, 7, 0, 0, 0, 1, 14, 16, 0 )
|
||||
layers = 1
|
||||
material_override = null
|
||||
cast_shadow = 1
|
||||
extra_cull_margin = 0.0
|
||||
use_in_baked_light = false
|
||||
lod_min_distance = 0.0
|
||||
lod_min_hysteresis = 0.0
|
||||
lod_max_distance = 0.0
|
||||
lod_max_hysteresis = 0.0
|
||||
mesh = SubResource( 5 )
|
||||
skeleton = NodePath("..")
|
||||
material/0 = null
|
||||
_sections_unfolded = [ "Transform" ]
|
||||
|
||||
[node name="DropTimer" type="Timer" parent="." index="5"]
|
||||
|
||||
process_mode = 1
|
||||
wait_time = 1.0
|
||||
one_shot = false
|
||||
autostart = false
|
||||
|
||||
[node name="LockDelay" type="Timer" parent="." index="6"]
|
||||
|
||||
process_mode = 1
|
||||
wait_time = 0.5
|
||||
one_shot = true
|
||||
autostart = false
|
||||
|
||||
[node name="AutoShiftDelay" type="Timer" parent="." index="7"]
|
||||
|
||||
process_mode = 1
|
||||
wait_time = 0.17
|
||||
one_shot = true
|
||||
autostart = false
|
||||
|
||||
[node name="AutoShiftTimer" type="Timer" parent="." index="8"]
|
||||
|
||||
process_mode = 1
|
||||
wait_time = 0.03
|
||||
one_shot = false
|
||||
autostart = false
|
||||
|
||||
[node name="MidiPlayer" parent="." index="9" instance=ExtResource( 5 )]
|
||||
|
||||
editor/display_folded = true
|
||||
script = ExtResource( 6 )
|
||||
file = "res://midi/Tetris - Song A.mid"
|
||||
volume_db = -12
|
||||
loop = true
|
||||
loop_start = 1.81
|
||||
soundfont = "res://midi/TimGM6mb.sf2"
|
||||
|
||||
[node name="LineCLearTimer" type="Timer" parent="MidiPlayer" index="1"]
|
||||
|
||||
process_mode = 1
|
||||
wait_time = 1.41
|
||||
one_shot = true
|
||||
autostart = false
|
||||
|
||||
[node name="FlashText" type="Control" parent="." index="10"]
|
||||
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -250.0
|
||||
margin_top = -250.0
|
||||
margin_right = 250.0
|
||||
margin_bottom = 250.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
script = ExtResource( 7 )
|
||||
_sections_unfolded = [ "Material", "Size Flags", "Theme" ]
|
||||
|
||||
[node name="Label" type="Label" parent="FlashText" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 190.0
|
||||
margin_right = 500.0
|
||||
margin_bottom = 690.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 6 )
|
||||
custom_colors/font_color = Color( 0.445404, 0.710476, 0.820313, 0 )
|
||||
align = 1
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_colors", "custom_fonts" ]
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="FlashText" index="1"]
|
||||
|
||||
root_node = NodePath("..")
|
||||
autoplay = ""
|
||||
playback_process_mode = 1
|
||||
playback_default_blend_time = 0.0
|
||||
playback_speed = 1.0
|
||||
anims/Flash = SubResource( 7 )
|
||||
blend_times = [ ]
|
||||
_sections_unfolded = [ "Playback Options" ]
|
||||
|
||||
[node name="Stats" parent="." index="11" instance=ExtResource( 9 )]
|
||||
|
||||
visible = false
|
||||
|
||||
[node name="controls_ui" parent="." index="12" instance=ExtResource( 10 )]
|
||||
|
||||
visible = false
|
||||
|
||||
[node name="ReplayButton" type="Button" parent="." index="13"]
|
||||
|
||||
visible = false
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = -100.0
|
||||
margin_top = -60.0
|
||||
margin_right = -27.0
|
||||
margin_bottom = -26.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
custom_fonts/font = SubResource( 8 )
|
||||
custom_colors/font_color = Color( 1, 1, 1, 1 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
text = "REPLAY"
|
||||
flat = false
|
||||
align = 1
|
||||
_sections_unfolded = [ "Margin", "custom_colors", "custom_fonts" ]
|
||||
|
||||
[node name="Start" parent="." index="14" instance=ExtResource( 12 )]
|
||||
|
||||
[connection signal="piece_dropped" from="." to="Stats" method="_on_Main_piece_dropped"]
|
||||
|
||||
[connection signal="piece_locked" from="." to="MidiPlayer" method="_on_Main_piece_locked"]
|
||||
|
||||
[connection signal="piece_locked" from="." to="Stats" method="_on_Main_piece_locked"]
|
||||
|
||||
[connection signal="timeout" from="DropTimer" to="." method="_on_DropTimer_timeout"]
|
||||
|
||||
[connection signal="timeout" from="LockDelay" to="." method="_on_LockDelay_timeout"]
|
||||
|
||||
[connection signal="timeout" from="AutoShiftDelay" to="." method="_on_AutoShiftDelay_timeout"]
|
||||
|
||||
[connection signal="timeout" from="AutoShiftTimer" to="." method="_on_AutoShiftTimer_timeout"]
|
||||
|
||||
[connection signal="timeout" from="MidiPlayer/LineCLearTimer" to="MidiPlayer" method="_on_LineCLearTimer_timeout"]
|
||||
|
||||
[connection signal="animation_finished" from="FlashText/AnimationPlayer" to="FlashText" method="_on_AnimationPlayer_animation_finished"]
|
||||
|
||||
[connection signal="flash_text" from="Stats" to="FlashText" method="print"]
|
||||
|
||||
[connection signal="level_up" from="Stats" to="." method="_on_Stats_level_up"]
|
||||
|
||||
[connection signal="pressed" from="ReplayButton" to="." method="_on_ReplayButton_pressed"]
|
||||
|
||||
[connection signal="start" from="Start" to="." method="_on_Start_start"]
|
||||
|
||||
|
||||
53
source/MidiPlayer.gd
Normal file
@@ -0,0 +1,53 @@
|
||||
extends "midi/MidiPlayer.gd"
|
||||
|
||||
const Tetromino = preload("res://Tetrominos/Tetromino.gd")
|
||||
|
||||
const LINE_CLEAR_CHANNELS = [2, 6]
|
||||
|
||||
var muted_events = []
|
||||
|
||||
func _ready():
|
||||
mute_channels(LINE_CLEAR_CHANNELS)
|
||||
|
||||
func _init_channel( ):
|
||||
._init_channel()
|
||||
for channel in max_channel:
|
||||
self.muted_events.append({})
|
||||
|
||||
func resume():
|
||||
play(position)
|
||||
|
||||
func _process_track_event_note_off( channel, event ):
|
||||
muted_events[channel.number].erase(event.note)
|
||||
._process_track_event_note_off( channel, event )
|
||||
|
||||
func _process_track_event_note_on( channel, event ):
|
||||
if self.channel_mute[channel.number]:
|
||||
muted_events[channel.number][event.note] = event
|
||||
._process_track_event_note_on( channel, event )
|
||||
|
||||
func mute_channels(channels):
|
||||
for channel_id in channels:
|
||||
channel_mute[channel_id] = true
|
||||
|
||||
func unmute_channels(channels):
|
||||
for channel_id in channels:
|
||||
channel_mute[channel_id] = false
|
||||
for note in muted_events[channel_id]:
|
||||
_process_track_event_note_on(channel_status[channel_id], muted_events[channel_id][note])
|
||||
|
||||
func _on_Main_piece_locked(lines, t_spin):
|
||||
if lines or t_spin:
|
||||
if lines == Tetromino.NB_MINOES:
|
||||
for channel in LINE_CLEAR_CHANNELS:
|
||||
channel_status[channel].vomume = 127
|
||||
$LineCLearTimer.wait_time = 0.86
|
||||
else:
|
||||
for channel in LINE_CLEAR_CHANNELS:
|
||||
channel_status[channel].vomume = 100
|
||||
$LineCLearTimer.wait_time = 0.43
|
||||
unmute_channels(LINE_CLEAR_CHANNELS)
|
||||
$LineCLearTimer.start()
|
||||
|
||||
func _on_LineCLearTimer_timeout():
|
||||
mute_channels(LINE_CLEAR_CHANNELS)
|
||||
6
source/Start.gd
Normal file
@@ -0,0 +1,6 @@
|
||||
extends Control
|
||||
|
||||
signal start(level)
|
||||
|
||||
func _on_PlayButton_pressed():
|
||||
emit_signal("start", $SpinBox.value)
|
||||
123
source/Start.tscn
Normal file
@@ -0,0 +1,123 @@
|
||||
[gd_scene load_steps=7 format=2]
|
||||
|
||||
[ext_resource path="res://Start.gd" type="Script" id=1]
|
||||
[ext_resource path="res://fonts/TitleFont.tres" type="DynamicFont" id=2]
|
||||
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=3]
|
||||
[ext_resource path="res://fonts/ButtonFont.tres" type="DynamicFont" id=4]
|
||||
|
||||
[sub_resource type="DynamicFont" id=1]
|
||||
|
||||
size = 20
|
||||
use_mipmaps = false
|
||||
use_filter = false
|
||||
font_data = ExtResource( 3 )
|
||||
_sections_unfolded = [ "Extra Spacing", "Font", "Font/fallback", "Settings" ]
|
||||
|
||||
[sub_resource type="Theme" id=2]
|
||||
|
||||
default_font = SubResource( 1 )
|
||||
|
||||
[node name="Start" type="Control" index="0"]
|
||||
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -20.0
|
||||
margin_top = -20.0
|
||||
margin_right = 20.0
|
||||
margin_bottom = 20.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Label" type="Label" parent="." index="0"]
|
||||
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.0
|
||||
margin_left = -250.0
|
||||
margin_top = -100.0
|
||||
margin_right = 250.0
|
||||
margin_bottom = -26.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = ExtResource( 2 )
|
||||
custom_colors/font_color = Color( 0.443137, 0.709804, 0.819608, 0.533333 )
|
||||
custom_colors/font_color_shadow = Color( 1, 1, 1, 0.101333 )
|
||||
text = "TETRIS
|
||||
3000"
|
||||
align = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_colors", "custom_fonts" ]
|
||||
|
||||
[node name="SpinBox" type="SpinBox" parent="." index="1"]
|
||||
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -83.0
|
||||
margin_top = -20.0
|
||||
margin_right = 113.0
|
||||
margin_bottom = 17.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
theme = SubResource( 2 )
|
||||
min_value = 1.0
|
||||
max_value = 15.0
|
||||
step = 1.0
|
||||
page = 0.0
|
||||
value = 1.0
|
||||
exp_edit = false
|
||||
rounded = false
|
||||
editable = true
|
||||
prefix = "Level "
|
||||
suffix = ""
|
||||
_sections_unfolded = [ "Margin", "Theme" ]
|
||||
|
||||
[node name="PlayButton" type="Button" parent="." index="2"]
|
||||
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -85.0
|
||||
margin_top = 29.0
|
||||
margin_right = 85.0
|
||||
margin_bottom = 69.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
text = "Play"
|
||||
flat = false
|
||||
align = 1
|
||||
_sections_unfolded = [ "custom_fonts" ]
|
||||
|
||||
[connection signal="pressed" from="PlayButton" to="." method="_on_PlayButton_pressed"]
|
||||
|
||||
|
||||
88
source/Stats.gd
Normal file
@@ -0,0 +1,88 @@
|
||||
extends MarginContainer
|
||||
|
||||
const SCORES = [
|
||||
[0, 4, 1],
|
||||
[1, 8, 2],
|
||||
[3, 12],
|
||||
[5, 16],
|
||||
[8]
|
||||
]
|
||||
const LINES_CLEARED_NAMES = ["", "SINGLE", "DOUBLE", "TRIPLE", "TETRIS"]
|
||||
const T_SPIN_NAMES = ["", "T-SPIN", "MINI T-SPIN"]
|
||||
|
||||
var level
|
||||
var goal
|
||||
var score
|
||||
var high_score
|
||||
var time
|
||||
var combos
|
||||
|
||||
signal flash_text(text)
|
||||
signal level_up
|
||||
|
||||
func new_game(start_level):
|
||||
level = start_level - 1
|
||||
goal = 0
|
||||
score = 0
|
||||
$VBC/Score.text = str(score)
|
||||
time = 0
|
||||
$VBC/Time.text = "0:00:00"
|
||||
combos = -1
|
||||
new_level()
|
||||
|
||||
func new_level():
|
||||
level += 1
|
||||
goal += 5 * level
|
||||
$VBC/Level.text = str(level)
|
||||
$VBC/Goal.text = str(goal)
|
||||
emit_signal("flash_text", "Level\n%d"%level)
|
||||
emit_signal("level_up")
|
||||
|
||||
func _on_Clock_timeout():
|
||||
show_time()
|
||||
|
||||
func show_time():
|
||||
var time_elapsed = OS.get_system_time_secs() - time
|
||||
var seconds = time_elapsed % 60
|
||||
var minutes = int(time_elapsed/60) % 60
|
||||
var hours = int(time_elapsed/3600)
|
||||
$VBC/Time.text = str(hours) + ":%02d"%minutes + ":%02d"%seconds
|
||||
|
||||
func _on_Main_piece_dropped(ds):
|
||||
score += ds
|
||||
$VBC/Score.text = str(score)
|
||||
|
||||
func _on_Main_piece_locked(lines, t_spin):
|
||||
var ds
|
||||
if lines or t_spin:
|
||||
var text = T_SPIN_NAMES[t_spin]
|
||||
if text:
|
||||
text += " "
|
||||
text += LINES_CLEARED_NAMES[lines]
|
||||
emit_signal("flash_text", text)
|
||||
ds = SCORES[lines][t_spin]
|
||||
goal -= ds
|
||||
$VBC/Goal.text = str(goal)
|
||||
ds *= 100
|
||||
emit_signal("flash_text", str(ds))
|
||||
score += ds
|
||||
$VBC/Score.text = str(score)
|
||||
if score > high_score:
|
||||
high_score = score
|
||||
$VBC/HighScore.text = str(high_score)
|
||||
# Combos
|
||||
if lines:
|
||||
combos += 1
|
||||
if combos > 0:
|
||||
if combos == 1:
|
||||
emit_signal("flash_text", "COMBO")
|
||||
else:
|
||||
emit_signal("flash_text", "COMBO x%d"%combos)
|
||||
ds = (20 if lines==1 else 50) * combos * level
|
||||
emit_signal("flash_text", str(ds))
|
||||
score += ds
|
||||
$VBC/Score.text = str(score)
|
||||
else:
|
||||
combos = -1
|
||||
if goal <= 0:
|
||||
new_level()
|
||||
290
source/Stats.tscn
Normal file
@@ -0,0 +1,290 @@
|
||||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://Stats.gd" type="Script" id=1]
|
||||
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=2]
|
||||
|
||||
[sub_resource type="DynamicFont" id=2]
|
||||
|
||||
size = 20
|
||||
use_mipmaps = false
|
||||
use_filter = false
|
||||
font_data = ExtResource( 2 )
|
||||
_sections_unfolded = [ "Font", "Settings" ]
|
||||
|
||||
[node name="Stats" type="MarginContainer" index="0"]
|
||||
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -230.0
|
||||
margin_top = -10.0
|
||||
margin_right = -120.0
|
||||
margin_bottom = 200.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 0
|
||||
script = ExtResource( 1 )
|
||||
_sections_unfolded = [ "Anchor", "Margin", "Size Flags" ]
|
||||
|
||||
[node name="Clock" type="Timer" parent="." index="0"]
|
||||
|
||||
process_mode = 1
|
||||
wait_time = 1.0
|
||||
one_shot = false
|
||||
autostart = false
|
||||
|
||||
[node name="VBC" type="VBoxContainer" parent="." index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 16.0
|
||||
margin_right = 94.0
|
||||
margin_bottom = 170.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 1
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 0
|
||||
custom_constants/separation = 0
|
||||
alignment = 0
|
||||
_sections_unfolded = [ "Anchor", "Margin", "Size Flags", "custom_constants" ]
|
||||
|
||||
[node name="Label" type="Label" parent="VBC" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 17.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "Score:"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_fonts" ]
|
||||
|
||||
[node name="Score" type="Label" parent="VBC" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 17.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 34.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "0"
|
||||
align = 2
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_fonts" ]
|
||||
|
||||
[node name="Label2" type="Label" parent="VBC" index="2"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 34.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 51.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "High score:"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "Anchor", "custom_fonts", "custom_styles" ]
|
||||
|
||||
[node name="HighScore" type="Label" parent="VBC" index="3"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 51.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 68.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "0"
|
||||
align = 2
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_colors" ]
|
||||
|
||||
[node name="Label3" type="Label" parent="VBC" index="4"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 68.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 85.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "Time"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Time" type="Label" parent="VBC" index="5"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 85.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 102.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "0:00:00"
|
||||
align = 2
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Label4" type="Label" parent="VBC" index="6"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 102.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 119.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "Level:"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Level" type="Label" parent="VBC" index="7"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 119.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 136.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "0"
|
||||
align = 2
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Label5" type="Label" parent="VBC" index="8"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 136.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 153.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "Goal:"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Goal" type="Label" parent="VBC" index="9"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 153.0
|
||||
margin_right = 78.0
|
||||
margin_bottom = 170.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
|
||||
text = "0"
|
||||
align = 2
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_fonts" ]
|
||||
|
||||
[connection signal="timeout" from="Clock" to="." method="_on_Clock_timeout"]
|
||||
|
||||
|
||||
228
source/Tetrominos/Mino/Mino.tscn
Normal file
@@ -0,0 +1,228 @@
|
||||
[gd_scene load_steps=8 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/Mino/MinoMesh.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/MinoMesh.tres" type="CubeMesh" id=2]
|
||||
|
||||
[sub_resource type="SpatialMaterial" id=1]
|
||||
|
||||
render_priority = 0
|
||||
flags_transparent = true
|
||||
flags_unshaded = false
|
||||
flags_vertex_lighting = false
|
||||
flags_no_depth_test = false
|
||||
flags_use_point_size = false
|
||||
flags_world_triplanar = false
|
||||
flags_fixed_size = false
|
||||
flags_albedo_tex_force_srgb = false
|
||||
vertex_color_use_as_albedo = false
|
||||
vertex_color_is_srgb = false
|
||||
params_diffuse_mode = 0
|
||||
params_specular_mode = 0
|
||||
params_blend_mode = 1
|
||||
params_cull_mode = 0
|
||||
params_depth_draw_mode = 0
|
||||
params_line_width = 1.0
|
||||
params_point_size = 1.0
|
||||
params_billboard_mode = 0
|
||||
params_grow = false
|
||||
params_use_alpha_scissor = false
|
||||
albedo_color = Color( 0.601563, 0.775878, 1, 0.270275 )
|
||||
metallic = 0.68
|
||||
metallic_specular = 1.0
|
||||
metallic_texture_channel = 0
|
||||
roughness = 0.0
|
||||
roughness_texture_channel = 0
|
||||
emission_enabled = false
|
||||
normal_enabled = false
|
||||
rim_enabled = false
|
||||
clearcoat_enabled = false
|
||||
anisotropy_enabled = false
|
||||
ao_enabled = false
|
||||
depth_enabled = false
|
||||
subsurf_scatter_enabled = false
|
||||
transmission_enabled = false
|
||||
refraction_enabled = false
|
||||
detail_enabled = false
|
||||
uv1_scale = Vector3( 1, 1, 1 )
|
||||
uv1_offset = Vector3( 0, 0, 0 )
|
||||
uv1_triplanar = false
|
||||
uv1_triplanar_sharpness = 1.0
|
||||
uv2_scale = Vector3( 1, 1, 1 )
|
||||
uv2_offset = Vector3( 0, 0, 0 )
|
||||
uv2_triplanar = false
|
||||
uv2_triplanar_sharpness = 1.0
|
||||
proximity_fade_enable = true
|
||||
proximity_fade_distance = 1.0
|
||||
distance_fade_enable = false
|
||||
_sections_unfolded = [ "Albedo", "Emission", "Metallic", "NormalMap", "Proximity Fade" ]
|
||||
|
||||
[sub_resource type="GradientTexture" id=2]
|
||||
|
||||
flags = 4
|
||||
width = 2048
|
||||
|
||||
[sub_resource type="ParticlesMaterial" id=3]
|
||||
|
||||
render_priority = 0
|
||||
trail_divisor = 1
|
||||
emission_shape = 0
|
||||
flag_align_y = false
|
||||
flag_rotate_y = false
|
||||
flag_disable_z = true
|
||||
spread = 0.0
|
||||
flatness = 0.0
|
||||
gravity = Vector3( 0, 0, 0 )
|
||||
initial_velocity = 0.0
|
||||
initial_velocity_random = 0.0
|
||||
angular_velocity = 0.0
|
||||
angular_velocity_random = 0.0
|
||||
orbit_velocity = 0.0
|
||||
orbit_velocity_random = 0.0
|
||||
linear_accel = 0.0
|
||||
linear_accel_random = 0.0
|
||||
radial_accel = 0.0
|
||||
radial_accel_random = 0.0
|
||||
tangential_accel = 0.0
|
||||
tangential_accel_random = 0.0
|
||||
damping = 0.0
|
||||
damping_random = 0.0
|
||||
angle = 0.0
|
||||
angle_random = 0.0
|
||||
scale = 1.0
|
||||
scale_random = 0.0
|
||||
color_ramp = SubResource( 2 )
|
||||
hue_variation = 0.0
|
||||
hue_variation_random = 0.0
|
||||
anim_speed = 0.0
|
||||
anim_speed_random = 0.0
|
||||
anim_offset = 0.0
|
||||
anim_offset_random = 0.0
|
||||
anim_loop = false
|
||||
_sections_unfolded = [ "Color", "Gravity" ]
|
||||
|
||||
[sub_resource type="SpatialMaterial" id=4]
|
||||
|
||||
render_priority = 0
|
||||
flags_transparent = true
|
||||
flags_unshaded = false
|
||||
flags_vertex_lighting = false
|
||||
flags_no_depth_test = false
|
||||
flags_use_point_size = false
|
||||
flags_world_triplanar = false
|
||||
flags_fixed_size = false
|
||||
flags_albedo_tex_force_srgb = false
|
||||
vertex_color_use_as_albedo = false
|
||||
vertex_color_is_srgb = false
|
||||
params_diffuse_mode = 0
|
||||
params_specular_mode = 0
|
||||
params_blend_mode = 1
|
||||
params_cull_mode = 0
|
||||
params_depth_draw_mode = 0
|
||||
params_line_width = 1.0
|
||||
params_point_size = 1.0
|
||||
params_billboard_mode = 0
|
||||
params_grow = false
|
||||
params_use_alpha_scissor = false
|
||||
albedo_color = Color( 0.601563, 0.775878, 1, 0.0045098 )
|
||||
metallic = 0.68
|
||||
metallic_specular = 1.0
|
||||
metallic_texture_channel = 0
|
||||
roughness = 0.0
|
||||
roughness_texture_channel = 0
|
||||
emission_enabled = false
|
||||
normal_enabled = false
|
||||
rim_enabled = false
|
||||
clearcoat_enabled = false
|
||||
anisotropy_enabled = false
|
||||
ao_enabled = false
|
||||
depth_enabled = false
|
||||
subsurf_scatter_enabled = false
|
||||
transmission_enabled = false
|
||||
refraction_enabled = false
|
||||
detail_enabled = false
|
||||
uv1_scale = Vector3( 1, 1, 1 )
|
||||
uv1_offset = Vector3( 0, 0, 0 )
|
||||
uv1_triplanar = false
|
||||
uv1_triplanar_sharpness = 1.0
|
||||
uv2_scale = Vector3( 1, 1, 1 )
|
||||
uv2_offset = Vector3( 0, 0, 0 )
|
||||
uv2_triplanar = false
|
||||
uv2_triplanar_sharpness = 1.0
|
||||
proximity_fade_enable = true
|
||||
proximity_fade_distance = 1.0
|
||||
distance_fade_enable = false
|
||||
_sections_unfolded = [ "Albedo", "Emission", "Proximity Fade" ]
|
||||
|
||||
[sub_resource type="CubeMesh" id=5]
|
||||
|
||||
material = SubResource( 4 )
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
size = Vector3( 0.9, 0.9, 0.9 )
|
||||
subdivide_width = 0
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
[node name="Mino" type="Spatial"]
|
||||
|
||||
transform = Transform( 0.997027, 0, 0, 0, 0.997027, 0, 0, 0, 0.997027, 0, 0, 0 )
|
||||
_sections_unfolded = [ "Pause", "Transform", "Visibility" ]
|
||||
|
||||
[node name="MinoMesh" parent="." index="0" instance=ExtResource( 1 )]
|
||||
|
||||
mesh = ExtResource( 2 )
|
||||
material/0 = null
|
||||
_sections_unfolded = [ "Geometry", "Transform", "material" ]
|
||||
|
||||
[node name="Trail" type="Particles" parent="." index="1"]
|
||||
|
||||
layers = 1
|
||||
material_override = SubResource( 1 )
|
||||
cast_shadow = 1
|
||||
extra_cull_margin = 0.0
|
||||
use_in_baked_light = false
|
||||
lod_min_distance = 0.0
|
||||
lod_min_hysteresis = 0.0
|
||||
lod_max_distance = 0.0
|
||||
lod_max_hysteresis = 0.0
|
||||
emitting = false
|
||||
amount = 4
|
||||
lifetime = 0.05
|
||||
one_shot = false
|
||||
preprocess = 0.0
|
||||
speed_scale = 1.0
|
||||
explosiveness = 0.0
|
||||
randomness = 0.0
|
||||
fixed_fps = 0
|
||||
fract_delta = true
|
||||
visibility_aabb = AABB( -0.5, -0.5, -0.5, 1, 1, 1 )
|
||||
local_coords = false
|
||||
draw_order = 0
|
||||
process_material = SubResource( 3 )
|
||||
draw_passes = 1
|
||||
draw_pass_1 = SubResource( 5 )
|
||||
_sections_unfolded = [ "Draw Passes", "Drawing", "Geometry", "LOD", "Process Material", "Time", "Transform" ]
|
||||
|
||||
[node name="SpotLight" type="SpotLight" parent="." index="2"]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, -4.37114e-008, 1, 0, -1, -4.37114e-008, 0, 0, 0.5 )
|
||||
layers = 1
|
||||
light_color = Color( 1, 1, 1, 1 )
|
||||
light_energy = 16.0
|
||||
light_indirect_energy = 1.0
|
||||
light_negative = false
|
||||
light_specular = 0.5
|
||||
light_bake_mode = 1
|
||||
light_cull_mask = -1
|
||||
shadow_enabled = false
|
||||
shadow_color = Color( 0, 0, 0, 1 )
|
||||
shadow_bias = 0.15
|
||||
shadow_contact = 0.0
|
||||
shadow_reverse_cull_face = false
|
||||
editor_only = false
|
||||
spot_range = 20.0
|
||||
spot_attenuation = 0.5
|
||||
spot_angle = 5.0
|
||||
spot_angle_attenuation = 2.0
|
||||
_sections_unfolded = [ "Light", "Spot", "Transform" ]
|
||||
|
||||
|
||||
31
source/Tetrominos/Mino/MinoLibrary.tres
Normal file
59
source/Tetrominos/Mino/MinoMaterial.tres
Normal file
@@ -0,0 +1,59 @@
|
||||
[gd_resource type="SpatialMaterial" format=2]
|
||||
|
||||
[resource]
|
||||
|
||||
render_priority = 0
|
||||
flags_transparent = true
|
||||
flags_unshaded = false
|
||||
flags_vertex_lighting = false
|
||||
flags_no_depth_test = false
|
||||
flags_use_point_size = false
|
||||
flags_world_triplanar = false
|
||||
flags_fixed_size = false
|
||||
flags_albedo_tex_force_srgb = false
|
||||
vertex_color_use_as_albedo = false
|
||||
vertex_color_is_srgb = false
|
||||
params_diffuse_mode = 0
|
||||
params_specular_mode = 0
|
||||
params_blend_mode = 1
|
||||
params_cull_mode = 0
|
||||
params_depth_draw_mode = 0
|
||||
params_line_width = 1.0
|
||||
params_point_size = 1.0
|
||||
params_billboard_mode = 0
|
||||
params_grow = false
|
||||
params_use_alpha_scissor = false
|
||||
albedo_color = Color( 0.601563, 0.775878, 1, 0.486471 )
|
||||
metallic = 1.0
|
||||
metallic_specular = 1.0
|
||||
metallic_texture_channel = 4
|
||||
roughness = 0.46
|
||||
roughness_texture_channel = 0
|
||||
emission_enabled = true
|
||||
emission = Color( 0.755859, 1, 0.914169, 1 )
|
||||
emission_energy = 0.2
|
||||
emission_operator = 0
|
||||
emission_on_uv2 = false
|
||||
normal_enabled = false
|
||||
rim_enabled = false
|
||||
clearcoat_enabled = false
|
||||
anisotropy_enabled = false
|
||||
ao_enabled = false
|
||||
depth_enabled = false
|
||||
subsurf_scatter_enabled = false
|
||||
transmission_enabled = false
|
||||
refraction_enabled = false
|
||||
detail_enabled = false
|
||||
uv1_scale = Vector3( 1, 1, 1 )
|
||||
uv1_offset = Vector3( 0, 0, 0 )
|
||||
uv1_triplanar = false
|
||||
uv1_triplanar_sharpness = 1.0
|
||||
uv2_scale = Vector3( 1, 1, 1 )
|
||||
uv2_offset = Vector3( 0, 0, 0 )
|
||||
uv2_triplanar = false
|
||||
uv2_triplanar_sharpness = 1.0
|
||||
proximity_fade_enable = true
|
||||
proximity_fade_distance = 1.0
|
||||
distance_fade_enable = false
|
||||
_sections_unfolded = [ "Albedo", "Emission", "Metallic", "NormalMap", "Proximity Fade" ]
|
||||
|
||||
13
source/Tetrominos/Mino/MinoMesh.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="CubeMesh" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/Mino/MinoMaterial.tres" type="Material" id=1]
|
||||
|
||||
[resource]
|
||||
|
||||
material = ExtResource( 1 )
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
size = Vector3( 0.9, 0.9, 0.9 )
|
||||
subdivide_width = 0
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
31
source/Tetrominos/Mino/MinoMesh.tscn
Normal file
@@ -0,0 +1,31 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/Mino/MinoMaterial.tres" type="Material" id=1]
|
||||
|
||||
|
||||
|
||||
[sub_resource type="CubeMesh" id=1]
|
||||
|
||||
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
|
||||
size = Vector3( 0.9, 0.9, 0.9 )
|
||||
subdivide_width = 0
|
||||
subdivide_height = 0
|
||||
subdivide_depth = 0
|
||||
|
||||
[node name="Mino" type="MeshInstance" index="0"]
|
||||
|
||||
layers = 1
|
||||
material_override = null
|
||||
cast_shadow = 1
|
||||
extra_cull_margin = 0.0
|
||||
use_in_baked_light = false
|
||||
lod_min_distance = 0.0
|
||||
lod_min_hysteresis = 0.0
|
||||
lod_max_distance = 0.0
|
||||
lod_max_hysteresis = 0.0
|
||||
mesh = SubResource( 1 )
|
||||
skeleton = NodePath("..")
|
||||
material/0 = ExtResource( 1 )
|
||||
_sections_unfolded = [ "Transform", "material" ]
|
||||
|
||||
|
||||
70
source/Tetrominos/TetroI.gd
Normal file
@@ -0,0 +1,70 @@
|
||||
extends "Tetromino.gd"
|
||||
|
||||
const CLOCKWISE = -1
|
||||
const COUNTERCLOCKWISE = 1
|
||||
const SUPER_ROTATION_SYSTEM = [
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(0, -1, 0),
|
||||
Vector3(-1, -1, 0),
|
||||
Vector3(2, -1, 0),
|
||||
Vector3(-1, 1, 0),
|
||||
Vector3(2, -2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(2, 0, 0),
|
||||
Vector3(-1, -1, 0),
|
||||
Vector3(2, 2, 0)
|
||||
],
|
||||
},
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(-2, 0, 0),
|
||||
Vector3(1, 1, 0),
|
||||
Vector3(-2, -2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(0, -1, 0),
|
||||
Vector3(-1, -1, 0),
|
||||
Vector3(2, -1, 0),
|
||||
Vector3(-1, 1, 0),
|
||||
Vector3(2, -2, 0)
|
||||
],
|
||||
},
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(0, 1, 0),
|
||||
Vector3(1, 1, 0),
|
||||
Vector3(-2, 1, 0),
|
||||
Vector3(1, -1, 0),
|
||||
Vector3(-2, 2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(-2, 0, 0),
|
||||
Vector3(1, 1, 0),
|
||||
Vector3(-2, -2, 0)
|
||||
],
|
||||
},
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(2, 0, 0),
|
||||
Vector3(-1, -1, 0),
|
||||
Vector3(2, 2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(0, 1, 0),
|
||||
Vector3(1, 1, 0),
|
||||
Vector3(-2, 1, 0),
|
||||
Vector3(1, -1, 0),
|
||||
Vector3(-2, 2, 0)
|
||||
],
|
||||
},
|
||||
]
|
||||
25
source/Tetrominos/TetroI.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/TetroI.gd" type="Script" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
|
||||
|
||||
|
||||
[node name="TetroI" type="Spatial" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
|
||||
|
||||
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
|
||||
|
||||
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0 )
|
||||
|
||||
|
||||
2
source/Tetrominos/TetroJ.gd
Normal file
@@ -0,0 +1,2 @@
|
||||
extends "Tetromino.gd"
|
||||
|
||||
25
source/Tetrominos/TetroJ.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/TetroJ.gd" type="Script" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
|
||||
|
||||
|
||||
[node name="TetroJ" type="Spatial" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 1, 0 )
|
||||
|
||||
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
|
||||
|
||||
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
|
||||
|
||||
|
||||
2
source/Tetrominos/TetroL.gd
Normal file
@@ -0,0 +1,2 @@
|
||||
extends "Tetromino.gd"
|
||||
|
||||
25
source/Tetrominos/TetroL.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/TetroL.gd" type="Script" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
|
||||
|
||||
|
||||
[node name="TetroL" type="Spatial" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
|
||||
|
||||
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
|
||||
|
||||
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 )
|
||||
|
||||
|
||||
4
source/Tetrominos/TetroO.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
extends "Tetromino.gd"
|
||||
|
||||
func rotate(direction):
|
||||
pass
|
||||
25
source/Tetrominos/TetroO.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/TetroO.gd" type="Script" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
|
||||
|
||||
|
||||
[node name="TetroO" type="Spatial" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
|
||||
|
||||
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 )
|
||||
|
||||
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
|
||||
|
||||
|
||||
2
source/Tetrominos/TetroS.gd
Normal file
@@ -0,0 +1,2 @@
|
||||
extends "Tetromino.gd"
|
||||
|
||||
25
source/Tetrominos/TetroS.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/TetroS.gd" type="Script" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
|
||||
|
||||
|
||||
[node name="TetroS" type="Spatial" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
|
||||
|
||||
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
|
||||
|
||||
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 )
|
||||
|
||||
|
||||
25
source/Tetrominos/TetroT.gd
Normal file
@@ -0,0 +1,25 @@
|
||||
extends "Tetromino.gd"
|
||||
|
||||
const T_SLOT = [
|
||||
Vector3(-1, 1, 0),
|
||||
Vector3(1, 1, 0),
|
||||
Vector3(1, -1, 0),
|
||||
Vector3(-1, -1, 0)
|
||||
]
|
||||
|
||||
func rotate(direction):
|
||||
var rotation_point = .rotate(direction)
|
||||
if rotation_point and t_spin != T_SPIN:
|
||||
var center = to_global(minoes[0].translation)
|
||||
var a = not grid_map.is_free_cell(center + T_SLOT[orientation])
|
||||
var b = not grid_map.is_free_cell(center + T_SLOT[(1+orientation)%4])
|
||||
var c = not grid_map.is_free_cell(center + T_SLOT[(2+orientation)%4])
|
||||
var d = not grid_map.is_free_cell(center + T_SLOT[(3+orientation)%4])
|
||||
if a and b and (c or d):
|
||||
t_spin = T_SPIN
|
||||
elif c and d and (a or b):
|
||||
if rotation_point == 5:
|
||||
t_spin = T_SPIN
|
||||
else:
|
||||
t_spin = MINI_T_SPIN
|
||||
return rotation_point
|
||||
25
source/Tetrominos/TetroT.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/TetroT.gd" type="Script" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
|
||||
|
||||
|
||||
[node name="TetroT" type="Spatial" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
|
||||
|
||||
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
|
||||
|
||||
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
|
||||
|
||||
|
||||
2
source/Tetrominos/TetroZ.gd
Normal file
@@ -0,0 +1,2 @@
|
||||
extends "Tetromino.gd"
|
||||
|
||||
25
source/Tetrominos/TetroZ.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://Tetrominos/TetroZ.gd" type="Script" id=1]
|
||||
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
|
||||
|
||||
|
||||
[node name="TetroZ" type="Spatial" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 1, 0 )
|
||||
|
||||
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
|
||||
|
||||
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
|
||||
|
||||
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
|
||||
|
||||
|
||||
131
source/Tetrominos/Tetromino.gd
Normal file
@@ -0,0 +1,131 @@
|
||||
extends Spatial
|
||||
|
||||
const NB_MINOES = 4
|
||||
const CLOCKWISE = -1
|
||||
const COUNTERCLOCKWISE = 1
|
||||
const NO_T_SPIN = 0
|
||||
const T_SPIN = 1
|
||||
const MINI_T_SPIN = 2
|
||||
const SUPER_ROTATION_SYSTEM = [
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(1, 1, 0),
|
||||
Vector3(0, -2, 0),
|
||||
Vector3(1, -2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(-1, 1, 0),
|
||||
Vector3(0, -2, 0),
|
||||
Vector3(-1, -2, 0)
|
||||
],
|
||||
},
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(1, -1, 0),
|
||||
Vector3(0, 2, 0),
|
||||
Vector3(1, 2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(1, -1, 0),
|
||||
Vector3(0, 2, 0),
|
||||
Vector3(1, 2, 0)
|
||||
],
|
||||
},
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(-1, 1, 0),
|
||||
Vector3(0, -2, 0),
|
||||
Vector3(-1, -2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(1, 0, 0),
|
||||
Vector3(1, 1, 0),
|
||||
Vector3(0, -2, 0),
|
||||
Vector3(1, -2, 0)
|
||||
],
|
||||
},
|
||||
{
|
||||
COUNTERCLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(-1, -1, 0),
|
||||
Vector3(0, 2, 0),
|
||||
Vector3(-1, 2, 0)
|
||||
],
|
||||
CLOCKWISE: [
|
||||
Vector3(0, 0, 0),
|
||||
Vector3(-1, 0, 0),
|
||||
Vector3(-1, -1, 0),
|
||||
Vector3(0, -2, 0),
|
||||
Vector3(-1, 2, 0)
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
var minoes
|
||||
var grid_map
|
||||
var orientation = 0
|
||||
var t_spin = NO_T_SPIN
|
||||
|
||||
func _ready():
|
||||
randomize()
|
||||
minoes = [$Mino0, $Mino1, $Mino2, $Mino3]
|
||||
grid_map = get_parent().get_node("GridMap")
|
||||
|
||||
func positions():
|
||||
var p = []
|
||||
for mino in minoes:
|
||||
p.append(to_global(mino.translation))
|
||||
return p
|
||||
|
||||
func rotated_positions(direction):
|
||||
var translations = [to_global(minoes[0].translation) ]
|
||||
for i in range(1, 4):
|
||||
var v = to_global(minoes[i].translation)
|
||||
v -= to_global(minoes[0].translation)
|
||||
v = Vector3(-1*direction*v.y, direction*v.x, 0)
|
||||
v += to_global(minoes[0].translation)
|
||||
translations.append(v)
|
||||
return translations
|
||||
|
||||
func apply_positions(positions):
|
||||
for i in range(NB_MINOES):
|
||||
minoes[i].translation = to_local(positions[i])
|
||||
|
||||
func move(movement):
|
||||
if grid_map.possible_positions(positions(), movement):
|
||||
translate(movement)
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func rotate(direction):
|
||||
var rotated_positions = rotated_positions(direction)
|
||||
var movements = SUPER_ROTATION_SYSTEM[orientation][direction]
|
||||
for i in range(movements.size()):
|
||||
if grid_map.possible_positions(rotated_positions, movements[i]):
|
||||
orientation -= direction
|
||||
orientation %= NB_MINOES
|
||||
apply_positions(rotated_positions)
|
||||
translate(movements[i])
|
||||
return i+1
|
||||
return 0
|
||||
|
||||
func emit_trail(visible):
|
||||
var trail
|
||||
for mino in minoes:
|
||||
trail = mino.get_node("Trail")
|
||||
trail.emitting = visible
|
||||
trail.visible = visible
|
||||
mino.get_node("SpotLight").visible = visible
|
||||
116
source/controls.gd
Normal file
@@ -0,0 +1,116 @@
|
||||
extends Control
|
||||
|
||||
# Note for the reader:
|
||||
#
|
||||
# This demo conveniently uses the same names for actions and for the container nodes
|
||||
# that hold each remapping button. This allow to get back to the button based simply
|
||||
# on the name of the corresponding action, but it might not be so simple in your project.
|
||||
#
|
||||
# A better approach for large-scale input remapping might be to do the connections between
|
||||
# buttons and wait_for_input through the code, passing as arguments both the name of the
|
||||
# action and the node, e.g.:
|
||||
# button.connect("pressed", self, "wait_for_input", [ button, action ])
|
||||
|
||||
# Constants
|
||||
const INPUT_ACTIONS = [
|
||||
"move_left",
|
||||
"move_right",
|
||||
"rotate_clockwise",
|
||||
"rotate_counterclockwise",
|
||||
"soft_drop",
|
||||
"hard_drop",
|
||||
"hold",
|
||||
"pause"
|
||||
]
|
||||
const CONFIG_FILE = "user://input.cfg"
|
||||
|
||||
# Member variables
|
||||
var action # To register the action the UI is currently handling
|
||||
var button # Button node corresponding to the above action
|
||||
var enable_resume = true
|
||||
|
||||
|
||||
# Load/save input mapping to a config file
|
||||
# Changes done while testing the demo will be persistent, saved to CONFIG_FILE
|
||||
|
||||
func load_config():
|
||||
var config = ConfigFile.new()
|
||||
var err = config.load(CONFIG_FILE)
|
||||
if err: # Assuming that file is missing, generate default config
|
||||
for action_name in INPUT_ACTIONS:
|
||||
var action_list = InputMap.get_action_list(action_name)
|
||||
# There could be multiple actions in the list, but we save the first one by default
|
||||
var scancode = OS.get_scancode_string(action_list[0].scancode)
|
||||
config.set_value("input", action_name, scancode)
|
||||
config.save(CONFIG_FILE)
|
||||
else: # ConfigFile was properly loaded, initialize InputMap
|
||||
for action_name in config.get_section_keys("input"):
|
||||
# Get the key scancode corresponding to the saved human-readable string
|
||||
var scancode = OS.find_scancode_from_string(config.get_value("input", action_name))
|
||||
# Create a new event object based on the saved scancode
|
||||
var event = InputEventKey.new()
|
||||
event.scancode = scancode
|
||||
# Replace old action (key) events by the new one
|
||||
for old_event in InputMap.get_action_list(action_name):
|
||||
if old_event is InputEventKey:
|
||||
InputMap.action_erase_event(action_name, old_event)
|
||||
InputMap.action_add_event(action_name, event)
|
||||
|
||||
|
||||
func save_to_config(section, key, value):
|
||||
"""Helper function to redefine a parameter in the settings file"""
|
||||
var config = ConfigFile.new()
|
||||
var err = config.load(CONFIG_FILE)
|
||||
if err:
|
||||
print("Error code when loading config file: ", err)
|
||||
else:
|
||||
config.set_value(section, key, value)
|
||||
config.save(CONFIG_FILE)
|
||||
|
||||
|
||||
# Input management
|
||||
|
||||
func wait_for_input(action_bind):
|
||||
action = action_bind
|
||||
# See note at the beginning of the script
|
||||
button = get_node("bindings").get_node(action).get_node("Button")
|
||||
#get_node("contextual_help").text = "Press a key to assign to the '" + action + "' action."
|
||||
enable_resume = false
|
||||
set_process_input(true)
|
||||
|
||||
|
||||
func _input(event):
|
||||
# Handle the first pressed key
|
||||
if event is InputEventKey:
|
||||
# Register the event as handled and stop polling
|
||||
get_tree().set_input_as_handled()
|
||||
set_process_input(false)
|
||||
# Reinitialise the contextual help label
|
||||
#get_node("contextual_help").text = "Click a key binding to reassign it, or press the Cancel action."
|
||||
|
||||
# Display the string corresponding to the pressed key
|
||||
var scancode = OS.get_scancode_string(event.scancode)
|
||||
button.text = scancode
|
||||
# Start by removing previously key binding(s)
|
||||
for old_event in InputMap.get_action_list(action):
|
||||
InputMap.action_erase_event(action, old_event)
|
||||
# Add the new key binding
|
||||
InputMap.action_add_event(action, event)
|
||||
save_to_config("input", action, scancode)
|
||||
enable_resume = true
|
||||
|
||||
|
||||
func _ready():
|
||||
# Load config if existing, if not it will be generated with default values
|
||||
load_config()
|
||||
# Initialise each button with the default key binding from InputMap
|
||||
for action in INPUT_ACTIONS:
|
||||
# We assume that the key binding that we want is the first one (0), if there are several
|
||||
var input_event = InputMap.get_action_list(action)[0]
|
||||
# See note at the beginning of the script
|
||||
var button = get_node("bindings").get_node(action).get_node("Button")
|
||||
button.text = OS.get_scancode_string(input_event.scancode)
|
||||
button.connect("pressed", self, "wait_for_input", [action])
|
||||
|
||||
# Do not start processing input until a button is pressed
|
||||
set_process_input(false)
|
||||
610
source/controls.tscn
Normal file
@@ -0,0 +1,610 @@
|
||||
[gd_scene load_steps=6 format=2]
|
||||
|
||||
[ext_resource path="res://controls.gd" type="Script" id=1]
|
||||
[ext_resource path="res://fonts/TitleFont.tres" type="DynamicFont" id=2]
|
||||
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=3]
|
||||
[ext_resource path="res://fonts/ButtonFont.tres" type="DynamicFont" id=4]
|
||||
|
||||
[sub_resource type="DynamicFont" id=2]
|
||||
|
||||
size = 20
|
||||
use_mipmaps = true
|
||||
use_filter = false
|
||||
extra_spacing_top = -4
|
||||
extra_spacing_bottom = -4
|
||||
font_data = ExtResource( 3 )
|
||||
_sections_unfolded = [ "Extra Spacing", "Font", "Settings" ]
|
||||
|
||||
[node name="controls_ui" type="Control"]
|
||||
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -30.0
|
||||
margin_top = 10.0
|
||||
margin_right = 10.0
|
||||
margin_bottom = 50.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"__editor_plugin_screen__": "2D"
|
||||
}
|
||||
|
||||
[node name="pause" type="Label" parent="." index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = -40.0
|
||||
margin_top = -150.0
|
||||
margin_right = 110.0
|
||||
margin_bottom = -111.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = ExtResource( 2 )
|
||||
custom_colors/font_color = Color( 0.443137, 0.709804, 0.819608, 0.533333 )
|
||||
custom_colors/font_color_shadow = Color( 1, 1, 1, 0.0968627 )
|
||||
text = "PAUSE"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_colors", "custom_fonts" ]
|
||||
|
||||
[node name="bindings" type="Control" parent="." index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = -190.0
|
||||
margin_top = -130.0
|
||||
margin_right = -150.0
|
||||
margin_bottom = -90.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="move_left" type="Control" parent="bindings" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 50.0
|
||||
margin_top = 50.0
|
||||
margin_right = 90.0
|
||||
margin_bottom = 90.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/move_left" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 69.0
|
||||
margin_bottom = 29.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "move
|
||||
left"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "Rect", "custom_fonts" ]
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/move_left" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 80.0
|
||||
margin_right = 155.0
|
||||
margin_bottom = 37.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
_sections_unfolded = [ "Rect" ]
|
||||
|
||||
[node name="move_right" type="Control" parent="bindings" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 226.0
|
||||
margin_top = 51.0
|
||||
margin_right = 266.0
|
||||
margin_bottom = 91.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/move_right" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 5.0
|
||||
margin_top = -1.0
|
||||
margin_right = 80.0
|
||||
margin_bottom = 31.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "move
|
||||
right"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/move_right" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 84.0
|
||||
margin_top = -1.0
|
||||
margin_right = 159.0
|
||||
margin_bottom = 36.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
[node name="rotate_clockwise" type="Control" parent="bindings" index="2"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 50.0
|
||||
margin_top = 100.0
|
||||
margin_right = 90.0
|
||||
margin_bottom = 140.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/rotate_clockwise" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 118.0
|
||||
margin_bottom = 32.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "rotate
|
||||
clockwise"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/rotate_clockwise" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 80.0
|
||||
margin_right = 155.0
|
||||
margin_bottom = 37.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
_sections_unfolded = [ "custom_fonts" ]
|
||||
|
||||
[node name="rotate_counterclockwise" type="Control" parent="bindings" index="3"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 226.0
|
||||
margin_top = 101.0
|
||||
margin_right = 266.0
|
||||
margin_bottom = 141.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/rotate_counterclockwise" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 5.0
|
||||
margin_top = -2.0
|
||||
margin_right = 177.0
|
||||
margin_bottom = 37.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "rotate
|
||||
counter
|
||||
clockwise"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/rotate_counterclockwise" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 84.0
|
||||
margin_top = -1.0
|
||||
margin_right = 159.0
|
||||
margin_bottom = 36.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
_sections_unfolded = [ "Rect" ]
|
||||
|
||||
[node name="soft_drop" type="Control" parent="bindings" index="4"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 50.0
|
||||
margin_top = 150.0
|
||||
margin_right = 90.0
|
||||
margin_bottom = 190.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/soft_drop" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 66.0
|
||||
margin_bottom = 32.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "soft
|
||||
drop"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/soft_drop" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 80.0
|
||||
margin_right = 155.0
|
||||
margin_bottom = 37.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
[node name="hard_drop" type="Control" parent="bindings" index="5"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 226.0
|
||||
margin_top = 151.0
|
||||
margin_right = 266.0
|
||||
margin_bottom = 191.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/hard_drop" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 5.0
|
||||
margin_top = -1.0
|
||||
margin_right = 75.0
|
||||
margin_bottom = 31.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "hard
|
||||
drop"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/hard_drop" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 84.0
|
||||
margin_top = -1.0
|
||||
margin_right = 159.0
|
||||
margin_bottom = 36.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
[node name="hold" type="Control" parent="bindings" index="6"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 50.0
|
||||
margin_top = 200.0
|
||||
margin_right = 90.0
|
||||
margin_bottom = 240.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/hold" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 55.0
|
||||
margin_bottom = 40.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "hold"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/hold" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 80.0
|
||||
margin_right = 155.0
|
||||
margin_bottom = 37.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
[node name="pause" type="Control" parent="bindings" index="7"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 226.0
|
||||
margin_top = 201.0
|
||||
margin_right = 266.0
|
||||
margin_bottom = 241.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="bindings/pause" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 5.0
|
||||
margin_top = 3.0
|
||||
margin_right = 106.0
|
||||
margin_bottom = 35.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 0
|
||||
custom_fonts/font = SubResource( 2 )
|
||||
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
|
||||
text = "pause
|
||||
resume"
|
||||
valign = 1
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
_sections_unfolded = [ "custom_fonts" ]
|
||||
|
||||
[node name="Button" type="Button" parent="bindings/pause" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 84.0
|
||||
margin_top = -1.0
|
||||
margin_right = 159.0
|
||||
margin_bottom = 36.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
custom_fonts/font = ExtResource( 4 )
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
|
||||
11
source/default_bus_layout.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="AudioBusLayout" format=2]
|
||||
|
||||
[resource]
|
||||
|
||||
bus/0/name = "Master"
|
||||
bus/0/solo = false
|
||||
bus/0/mute = false
|
||||
bus/0/bypass_fx = false
|
||||
bus/0/volume_db = 0.0
|
||||
bus/0/send = ""
|
||||
|
||||
BIN
source/fonts/525-ROUN.TTF
Normal file
13
source/fonts/ButtonFont.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
|
||||
size = 20
|
||||
use_mipmaps = false
|
||||
use_filter = false
|
||||
extra_spacing_top = -4
|
||||
font_data = ExtResource( 1 )
|
||||
_sections_unfolded = [ "Extra Spacing", "Font", "Settings" ]
|
||||
|
||||
BIN
source/fonts/Gamer.ttf
Normal file
12
source/fonts/TitleFont.tres
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://fonts/525-ROUN.TTF" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
|
||||
size = 30
|
||||
use_mipmaps = false
|
||||
use_filter = false
|
||||
font_data = ExtResource( 1 )
|
||||
_sections_unfolded = [ "Font", "Settings" ]
|
||||
|
||||
BIN
source/icons/16.png
Normal file
|
After Width: | Height: | Size: 208 B |
BIN
source/icons/256.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
source/icons/32.png
Normal file
|
After Width: | Height: | Size: 384 B |
BIN
source/icons/48.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
source/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 211 KiB |
96
source/midi/ADSR.gd
Normal file
@@ -0,0 +1,96 @@
|
||||
extends AudioStreamPlayer
|
||||
|
||||
"""
|
||||
AudioStreamPlayer with ADSR
|
||||
"""
|
||||
|
||||
var releasing = false
|
||||
var instrument = null
|
||||
var velocity = 0
|
||||
var pitch_bend = 0
|
||||
var mix_rate = 0
|
||||
var using_timer = 0.0
|
||||
var timer = 0.0
|
||||
var current_volume = 0
|
||||
var maximum_volume_db = -8.0
|
||||
var minimum_volume_db = -108.0
|
||||
var pan = 0.5
|
||||
var ads_state = [
|
||||
{ "time": 0, "volume": 1.0 },
|
||||
{ "time": 0.2, "volume": 0.95 },
|
||||
# { "time": 0.2, "jump_to": 0.0 }, # not implemented
|
||||
]
|
||||
var release_state = [
|
||||
{ "time": 0, "volume": 0.8 },
|
||||
{ "time": 0.01, "volume": 0.0 },
|
||||
# { "time": 0.2, "jump_to": 0.0 }, # not implemented
|
||||
]
|
||||
|
||||
func _ready( ):
|
||||
self.stop( )
|
||||
|
||||
func set_instrument( instrument ):
|
||||
self.instrument = instrument
|
||||
self.mix_rate = instrument.mix_rate
|
||||
self.stream = instrument.stream.duplicate( )
|
||||
self.ads_state = instrument.ads_state
|
||||
self.release_state = instrument.release_state
|
||||
|
||||
func play( ):
|
||||
self.releasing = false
|
||||
self.timer = 0.0
|
||||
self.using_timer = 0.0
|
||||
self.current_volume = self.ads_state[0].volume
|
||||
self.stream.mix_rate = round( self.mix_rate * ( 1.0 + self.pitch_bend * 0.5 ) )
|
||||
.play( 0.0 )
|
||||
self._update_volume( )
|
||||
|
||||
func start_release( ):
|
||||
self.releasing = true
|
||||
self.current_volume = self.release_state[0].volume
|
||||
self.timer = 0.0
|
||||
self._update_volume( )
|
||||
|
||||
func set_pitch_bend( pb ):
|
||||
self.pitch_bend = pb
|
||||
var pos = self.get_playback_position( )
|
||||
self.stream.mix_rate = round( self.mix_rate * ( 1.0 + self.pitch_bend * 0.5 ) )
|
||||
.play( pos )
|
||||
|
||||
func _process( delta ):
|
||||
if not self.playing:
|
||||
return
|
||||
|
||||
self.timer += delta
|
||||
self.using_timer += delta
|
||||
# self.transform.origin.x = self.pan * self.get_viewport( ).size.x
|
||||
|
||||
# ADSR
|
||||
var use_state = null
|
||||
if self.releasing:
|
||||
use_state = self.release_state
|
||||
else:
|
||||
use_state = self.ads_state
|
||||
|
||||
var all_states = use_state.size( )
|
||||
var last_state = all_states - 1
|
||||
if use_state[last_state].time <= self.timer:
|
||||
self.current_volume = use_state[last_state].volume
|
||||
if self.releasing:
|
||||
self.stop( )
|
||||
else:
|
||||
for state_number in range( 1, all_states ):
|
||||
var state = use_state[state_number]
|
||||
if self.timer < state.time:
|
||||
var pre_state = use_state[state_number-1]
|
||||
var s = ( state.time - self.timer ) / ( state.time - pre_state.time )
|
||||
var t = 1.0 - s
|
||||
self.current_volume = pre_state.volume * s + state.volume * t
|
||||
break
|
||||
|
||||
self._update_volume( )
|
||||
|
||||
func _update_volume( ):
|
||||
var s = self.current_volume
|
||||
var t = 1.0 - s
|
||||
self.volume_db = s * self.maximum_volume_db + t * self.minimum_volume_db
|
||||
15
source/midi/ADSR.tscn
Normal file
@@ -0,0 +1,15 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://midi/ADSR.gd" type="Script" id=1]
|
||||
|
||||
[node name="ADSR" type="AudioStreamPlayer"]
|
||||
|
||||
stream = null
|
||||
volume_db = 0.0
|
||||
autoplay = false
|
||||
mix_target = 0
|
||||
bus = "Master"
|
||||
script = ExtResource( 1 )
|
||||
_sections_unfolded = [ "Pause", "Transform", "Z Index" ]
|
||||
|
||||
|
||||
326
source/midi/Bank.gd
Normal file
@@ -0,0 +1,326 @@
|
||||
"""
|
||||
Sound Bank by Yui Kinomoto @arlez80
|
||||
"""
|
||||
|
||||
const drum_track_bank = 128
|
||||
const SoundFont = preload( "SoundFont.gd" )
|
||||
|
||||
# デフォルト
|
||||
var default_mix_rate_table = [819,868,920,974,1032,1094,1159,1228,1301,1378,1460,1547,1639,1736,1840,1949,2065,2188,2318,2456,2602,2756,2920,3094,3278,3473,3679,3898,4130,4375,4635,4911,5203,5513,5840,6188,6556,6945,7358,7796,8259,8751,9271,9822,10406,11025,11681,12375,13111,13891,14717,15592,16519,17501,18542,19644,20812,22050,23361,24750,26222,27781,29433,31183,33038,35002,37084,39289,41625,44100,46722,49501,52444,55563,58866,62367,66075,70004,74167,78577,83250,88200,93445,99001,104888,111125,117733,124734,132151,140009,148334,157155,166499,176400,186889,198002,209776,222250,235466,249467,264301,280018,296668,314309,332999,352800,373779,396005,419552,444500,470932,498935,528603,560035,593337,628618,665998,705600,747557,792009,839105,889000,941863,997869,1057205,1120070,1186673,1257236]
|
||||
var default_ads_state = [
|
||||
{ "time": 0, "volume": 1.0 },
|
||||
{ "time": 0.2, "volume": 0.95 },
|
||||
]
|
||||
var default_release_state = [
|
||||
{ "time": 0, "volume": 0.95 },
|
||||
{ "time": 0.01, "volume": 0.0 },
|
||||
]
|
||||
|
||||
# 音色テーブル
|
||||
var presets = {}
|
||||
|
||||
"""
|
||||
楽器
|
||||
"""
|
||||
func create_preset( ):
|
||||
var instruments = []
|
||||
for i in range( 0, 128 ):
|
||||
instruments.append( null )
|
||||
return {
|
||||
"name": "",
|
||||
"number": 0,
|
||||
"instruments": instruments,
|
||||
"bags": [],
|
||||
}
|
||||
|
||||
"""
|
||||
ノート
|
||||
"""
|
||||
func create_instrument( ):
|
||||
return {
|
||||
"mix_rate": 44100,
|
||||
"stream": null,
|
||||
"ads_state": default_ads_state,
|
||||
"release_state": default_release_state,
|
||||
"preset": null,
|
||||
# "assine_group": 0, # reserved
|
||||
}
|
||||
|
||||
"""
|
||||
再生周波数計算
|
||||
"""
|
||||
func calc_mix_rate( rate, center_key, target_key ):
|
||||
return round( rate * pow( 2.0, ( target_key - center_key ) / 12.0 ) )
|
||||
|
||||
"""
|
||||
追加
|
||||
"""
|
||||
func set_preset_sample( program_number, base_sample, base_mix_rate ):
|
||||
var mix_rate_table = default_mix_rate_table
|
||||
|
||||
if base_mix_rate != 44100:
|
||||
print( "not implemented" )
|
||||
breakpoint
|
||||
|
||||
var preset = self.create_preset( )
|
||||
preset.name = "#%03d" % program_number
|
||||
preset.number = program_number
|
||||
for i in range(0,128):
|
||||
var inst = self.create_instrument( )
|
||||
inst.mix_rate = mix_rate_table[i]
|
||||
inst.stream = base_sample
|
||||
inst.preset = preset
|
||||
preset.instruments[i] = inst
|
||||
|
||||
self.set_preset( program_number, preset )
|
||||
|
||||
"""
|
||||
追加
|
||||
"""
|
||||
func set_preset( program_number, preset ):
|
||||
self.presets[program_number] = preset
|
||||
|
||||
"""
|
||||
指定した楽器を取得
|
||||
"""
|
||||
func get_preset( program_number, bank = 0 ):
|
||||
var pc = program_number | ( bank << 7 )
|
||||
|
||||
if not self.presets.has( pc ):
|
||||
if drum_track_bank == bank:
|
||||
pc = drum_track_bank
|
||||
else:
|
||||
pc = program_number
|
||||
if not self.presets.has( pc ):
|
||||
pc = self.presets.keys( )[0]
|
||||
|
||||
var preset = self.presets[pc]
|
||||
return preset
|
||||
|
||||
"""
|
||||
サウンドフォント読み込み
|
||||
"""
|
||||
func read_soundfont( sf, need_program_numbers = null ):
|
||||
var sf_insts = self._read_soundfont_pdta_inst( sf )
|
||||
|
||||
var bag_index = 0
|
||||
var gen_index = 0
|
||||
for phdr_index in range( 0, len( sf.pdta.phdr )-1 ):
|
||||
var phdr = sf.pdta.phdr[phdr_index]
|
||||
|
||||
var preset = self.create_preset( )
|
||||
var program_number = phdr.preset | ( phdr.bank << 7 )
|
||||
|
||||
preset.name = phdr.name
|
||||
preset.number = program_number
|
||||
|
||||
var bag_next = sf.pdta.phdr[phdr_index+1].preset_bag_index
|
||||
var bag_count = bag_index
|
||||
while bag_count < bag_next:
|
||||
var gen_next = sf.pdta.pbag[bag_count+1].gen_ndx
|
||||
var gen_count = gen_index
|
||||
var bag = {
|
||||
"preset": preset,
|
||||
"coarse_tune": 0,
|
||||
"fine_tune": 0,
|
||||
"key_range": null,
|
||||
"instrument": null,
|
||||
"pan": 0.5,
|
||||
}
|
||||
while gen_count < gen_next:
|
||||
var gen = sf.pdta.pgen[gen_count]
|
||||
match gen.gen_oper:
|
||||
SoundFont.gen_oper_coarse_tune:
|
||||
bag.coarse_tune = gen.amount
|
||||
SoundFont.gen_oper_fine_tune:
|
||||
bag.fine_tune = gen.amount
|
||||
SoundFont.gen_oper_key_range:
|
||||
bag.key_range = {
|
||||
"high": gen.uamount >> 8,
|
||||
"low": gen.uamount & 0xFF,
|
||||
}
|
||||
SoundFont.gen_oper_pan:
|
||||
bag.pan = ( gen.amount + 500 ) / 1000.0
|
||||
SoundFont.gen_oper_instrument:
|
||||
bag.instrument = sf_insts[gen.uamount]
|
||||
gen_count += 1
|
||||
if bag.instrument != null:
|
||||
preset.bags.append( bag )
|
||||
gen_index = gen_next
|
||||
bag_count += 1
|
||||
bag_index = bag_next
|
||||
|
||||
# 追加するか?
|
||||
if need_program_numbers != null:
|
||||
if not( program_number in need_program_numbers ) and not( phdr.preset in need_program_numbers ):
|
||||
continue
|
||||
# 追加
|
||||
self._read_soundfont_preset_compose_sample( sf, preset )
|
||||
self.set_preset( program_number, preset )
|
||||
|
||||
func _read_soundfont_preset_compose_sample( sf, preset ):
|
||||
"""
|
||||
var samples = []
|
||||
var zero_4bytes = PoolIntArray( [ 0, 0 ] )
|
||||
for bag in preset.bags[0].instrument.bags:
|
||||
var sample = PoolIntArray( )
|
||||
var start = bag.sample.start + bag.sample_start_offset
|
||||
var end = bag.sample.end + bag.sample_end_offset
|
||||
for i in range( 0, ( end - start ) / 2 ):
|
||||
sample.append_array( zero_4bytes )
|
||||
samples.append( sample )
|
||||
"""
|
||||
|
||||
for pbag_index in range( 0, preset.bags.size( ) ):
|
||||
var pbag = preset.bags[pbag_index]
|
||||
for ibag_index in range( 0, pbag.instrument.bags.size( ) ):
|
||||
var ibag = pbag.instrument.bags[ibag_index]
|
||||
var start = ibag.sample.start + ibag.sample_start_offset
|
||||
var end = ibag.sample.end + ibag.sample_end_offset
|
||||
var start_loop = ibag.sample.start_loop + ibag.sample_start_loop_offset
|
||||
var end_loop = ibag.sample.end_loop + ibag.sample_end_loop_offset
|
||||
var mix_rate = ibag.sample.sample_rate * pow( 2.0, ( pbag.coarse_tune + ibag.coarse_tune ) / 12.0 ) * pow( 2.0, ( pbag.fine_tune + ibag.sample.pitch_correction + ibag.fine_tune ) / 1200.0 )
|
||||
|
||||
var ass = AudioStreamSample.new( )
|
||||
ass.data = sf.sdta.smpl.subarray( start * 2, end * 2 )
|
||||
ass.format = AudioStreamSample.FORMAT_16_BITS
|
||||
ass.mix_rate = mix_rate
|
||||
ass.stereo = false #bag.sample.sample_type != SoundFont.sample_link_mono_sample
|
||||
ass.loop_begin = start_loop - start
|
||||
ass.loop_end = end_loop - start
|
||||
if ibag.sample_modes == SoundFont.sample_mode_no_loop or ibag.sample_modes == SoundFont.sample_mode_unused_no_loop:
|
||||
ass.loop_mode = AudioStreamSample.LOOP_DISABLED
|
||||
else:
|
||||
ass.loop_mode = AudioStreamSample.LOOP_FORWARD
|
||||
var key_range = ibag.key_range
|
||||
if pbag.key_range != null:
|
||||
key_range = pbag.key_range
|
||||
for key_number in range( key_range.low, key_range.high + 1 ):
|
||||
#if preset.number == drum_track_bank << 7:
|
||||
# if 36 <= key_number and key_number <= 40:
|
||||
# print( key_number, " # ", ibag.sample.name );
|
||||
if preset.instruments[key_number] != null:
|
||||
continue
|
||||
var instrument = self.create_instrument( )
|
||||
instrument.preset = preset
|
||||
if ibag.original_key == 255:
|
||||
instrument.mix_rate = ibag.sample.sample_rate
|
||||
else:
|
||||
instrument.mix_rate = self.calc_mix_rate( mix_rate, ibag.original_key, key_number )
|
||||
instrument.stream = ass
|
||||
|
||||
var a = ibag.adsr.attack_vol_env_time
|
||||
var d = ibag.adsr.decay_vol_env_time
|
||||
var s = ibag.adsr.sustain_vol_env_level
|
||||
var r = ibag.adsr.release_vol_env_time
|
||||
instrument.ads_state = [
|
||||
{ "time": 0, "volume": 0.0 },
|
||||
{ "time": a, "volume": 1.0 },
|
||||
{ "time": a+d, "volume": s },
|
||||
]
|
||||
instrument.release_state = [
|
||||
{ "time": 0, "volume": s },
|
||||
{ "time": r, "volume": 0.0 },
|
||||
]
|
||||
preset.instruments[key_number] = instrument
|
||||
|
||||
func _read_soundfont_pdta_inst( sf ):
|
||||
var sf_insts = []
|
||||
var bag_index = 0
|
||||
var gen_index = 0
|
||||
|
||||
for inst_index in range(0, len( sf.pdta.inst ) - 1 ):
|
||||
var inst = sf.pdta.inst[inst_index]
|
||||
var sf_inst = {"name": inst.name, "bags": [] }
|
||||
|
||||
var bag_next = sf.pdta.inst[inst_index+1].inst_bag_ndx
|
||||
var bag_count = bag_index
|
||||
var global_bag = {}
|
||||
while bag_count < bag_next:
|
||||
var bag = {
|
||||
"sample": null,
|
||||
"sample_id": -1,
|
||||
"sample_start_offset": 0,
|
||||
"sample_end_offset": 0,
|
||||
"sample_start_loop_offset": 0,
|
||||
"sample_end_loop_offset": 0,
|
||||
"coarse_tune": 0,
|
||||
"fine_tune": 0,
|
||||
"original_key": 255,
|
||||
"keynum": 0,
|
||||
"sample_modes": 0,
|
||||
"key_range": { "high": 127, "low": 0 },
|
||||
"vel_range": { "high": 127, "low": 0 },
|
||||
"adsr": {
|
||||
"attack_vol_env_time": 0.001,
|
||||
"decay_vol_env_time": 0.001,
|
||||
"sustain_vol_env_level": 1.0,
|
||||
"release_vol_env_time": 0.001,
|
||||
},
|
||||
}
|
||||
var gen_next = sf.pdta.ibag[bag_count+1].gen_ndx
|
||||
var gen_count = gen_index
|
||||
while gen_count < gen_next:
|
||||
var gen = sf.pdta.igen[gen_count]
|
||||
match gen.gen_oper:
|
||||
SoundFont.gen_oper_key_range:
|
||||
bag.key_range.high = gen.uamount >> 8
|
||||
bag.key_range.low = gen.uamount & 0xFF
|
||||
SoundFont.gen_oper_vel_range:
|
||||
bag.vel_range.high = gen.uamount >> 8
|
||||
bag.vel_range.low = gen.uamount & 0xFF
|
||||
SoundFont.gen_oper_overriding_root_key:
|
||||
bag.original_key = gen.amount
|
||||
SoundFont.gen_oper_start_addrs_offset:
|
||||
bag.sample_start_offset += gen.amount
|
||||
SoundFont.gen_oper_end_addrs_offset:
|
||||
bag.sample_end_offset += gen.amount
|
||||
SoundFont.gen_oper_start_addrs_coarse_offset:
|
||||
bag.sample_start_offset += gen.amount * 32768
|
||||
SoundFont.gen_oper_end_addrs_coarse_offset:
|
||||
bag.sample_end_offset += gen.amount * 32768
|
||||
SoundFont.gen_oper_startloop_addrs_offset:
|
||||
bag.sample_start_loop_offset += gen.amount
|
||||
SoundFont.gen_oper_endloop_addrs_offset:
|
||||
bag.sample_end_loop_offset += gen.amount
|
||||
SoundFont.gen_oper_startloop_addrs_coarse_offset:
|
||||
bag.sample_start_loop_offset += gen.amount * 32768
|
||||
SoundFont.gen_oper_endloop_addrs_coarse_offset:
|
||||
bag.sample_end_loop_offset += gen.amount * 32768
|
||||
SoundFont.gen_oper_coarse_tune:
|
||||
bag.coarse_tune = gen.amount
|
||||
SoundFont.gen_oper_fine_tune:
|
||||
bag.fine_tune = gen.amount
|
||||
SoundFont.gen_oper_keynum:
|
||||
bag.keynum = gen.amount
|
||||
SoundFont.gen_oper_attack_vol_env:
|
||||
bag.adsr.attack_vol_env_time = pow( 2.0, gen.amount / 1200.0 )
|
||||
SoundFont.gen_oper_decay_vol_env:
|
||||
bag.adsr.decay_vol_env_time = pow( 2.0, gen.amount / 1200.0 )
|
||||
SoundFont.gen_oper_release_vol_env:
|
||||
bag.adsr.release_vol_env_time = pow( 2.0, gen.amount / 1200.0 )
|
||||
SoundFont.gen_oper_sustain_vol_env:
|
||||
var s = min( max( 0, gen.amount ), 1440 )
|
||||
bag.adsr.sustain_vol_env_level = ( 1440.0 - s ) / 1440.0
|
||||
SoundFont.gen_oper_sample_modes:
|
||||
bag.sample_modes = gen.uamount
|
||||
SoundFont.gen_oper_sample_id:
|
||||
bag.sample_id = gen.uamount
|
||||
bag.sample = sf.pdta.shdr[gen.amount]
|
||||
if bag.original_key == 255:
|
||||
bag.original_key = bag.sample.original_key
|
||||
#_:
|
||||
# print( gen.gen_oper )
|
||||
gen_count += 1
|
||||
# global zoneでない場合
|
||||
if bag.sample != null:
|
||||
sf_inst.bags.append( bag )
|
||||
else:
|
||||
global_bag = bag
|
||||
gen_index = gen_next
|
||||
bag_count += 1
|
||||
sf_insts.append( sf_inst )
|
||||
bag_index = bag_next
|
||||
|
||||
return sf_insts
|
||||
|
||||
140
source/midi/MOD.gd
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
MOD reader by Yui Kinomoto @arlez80
|
||||
"""
|
||||
|
||||
"""
|
||||
ファイルから読み込み
|
||||
@param path File path
|
||||
@return smf
|
||||
"""
|
||||
func read_file( path ):
|
||||
var f = File.new( )
|
||||
|
||||
if not f.file_exists( path ):
|
||||
print( "file %s is not found" % path )
|
||||
breakpoint
|
||||
|
||||
f.open( path, f.READ )
|
||||
var stream = StreamPeerBuffer.new( )
|
||||
stream.set_data_array( f.get_buffer( f.get_len( ) ) )
|
||||
stream.big_endian = true
|
||||
f.close( )
|
||||
|
||||
return self._read( stream )
|
||||
|
||||
"""
|
||||
配列から読み込み
|
||||
@param data PoolByteArray
|
||||
@return smf
|
||||
"""
|
||||
func read_data( data ):
|
||||
var stream = StreamPeerBuffer.new( )
|
||||
stream.set_data_array( data )
|
||||
stream.big_endian = true
|
||||
return self._read( stream )
|
||||
|
||||
"""
|
||||
読み込み
|
||||
@param stream
|
||||
@return smf
|
||||
"""
|
||||
func _read( stream ):
|
||||
var name = self._read_string( stream, 20 )
|
||||
var samples = self._read_sample_informations( stream )
|
||||
var song_length = stream.get_u8( )
|
||||
var unknown_number = stream.get_u8( )
|
||||
var song_positions = stream.get_partial_data( 128 )[1]
|
||||
var max_song_position = 0
|
||||
for sp in song_positions:
|
||||
if max_song_position < sp:
|
||||
max_song_position = sp
|
||||
|
||||
var magic = self._read_string( stream, 4 )
|
||||
var channel_count = 4
|
||||
match magic:
|
||||
"6CHN":
|
||||
channel_count = 6
|
||||
"FLT8", "8CHN", "CD81", "OKTA":
|
||||
channel_count = 8
|
||||
"16CN":
|
||||
channel_count = 16
|
||||
"32CN":
|
||||
channel_count = 32
|
||||
_:
|
||||
# print( "Unknown magic" )
|
||||
# breakpoint
|
||||
pass
|
||||
|
||||
var patterns = self._read_patterns( stream, max_song_position, channel_count )
|
||||
self._read_sample_data( stream, samples )
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"song_length": song_length,
|
||||
"unknown_number": unknown_number,
|
||||
"song_positions": song_positions,
|
||||
"magic": magic,
|
||||
|
||||
"patterns": patterns,
|
||||
"samples": samples,
|
||||
}
|
||||
|
||||
"""
|
||||
サンプルのデータを読み込む
|
||||
"""
|
||||
func _read_sample_informations( stream ):
|
||||
var samples = []
|
||||
|
||||
for i in range( 0, 31 ):
|
||||
var sample = {}
|
||||
sample.name = self._read_string( stream, 22 )
|
||||
sample.length = stream.get_u16( ) * 2
|
||||
sample.fine_tune = stream.get_u8( ) & 0x0F
|
||||
if 0x08 < sample.fine_tune:
|
||||
sample.fine_tune = 0x10 - sample.fine_tune
|
||||
sample.volume = stream.get_u8( )
|
||||
sample.loop_start = stream.get_u16( ) * 2
|
||||
sample.loop_length = stream.get_u16( ) * 2
|
||||
|
||||
samples.append( sample )
|
||||
|
||||
return samples
|
||||
|
||||
"""
|
||||
パターンを読み込む
|
||||
"""
|
||||
func _read_patterns( stream, max_position, channels ):
|
||||
var patterns = []
|
||||
for position in range( 0, max_position ):
|
||||
var pattern = []
|
||||
for i in range( 0, 64 ):
|
||||
var line = []
|
||||
for ch in range( 0, channels ):
|
||||
var v1 = stream.get_u16( )
|
||||
var v2 = stream.get_u16( )
|
||||
var sample_number = ( ( v1 >> 8 ) & 0xF0 ) | ( ( v2 >> 12 ) & 0x0F )
|
||||
var key_number = v1 & 0x0FFF
|
||||
var effect_command = v2 & 0x0FFF
|
||||
line.append({
|
||||
"sample_number": sample_number,
|
||||
"key_number": key_number,
|
||||
"effect_command": effect_command,
|
||||
})
|
||||
pattern.append( line )
|
||||
patterns.append( pattern )
|
||||
|
||||
"""
|
||||
波形データ読み込む
|
||||
"""
|
||||
func _read_sample_data( stream, samples ):
|
||||
for sample in samples:
|
||||
sample.data = stream.get_partial_data( sample.length )[1]
|
||||
|
||||
"""
|
||||
文字列の読み込み
|
||||
@param stream Stream
|
||||
@param size string size
|
||||
@return string
|
||||
"""
|
||||
func _read_string( stream, size ):
|
||||
return stream.get_partial_data( size )[1].get_string_from_ascii( )
|
||||
400
source/midi/MidiPlayer.gd
Normal file
@@ -0,0 +1,400 @@
|
||||
extends Node
|
||||
|
||||
const max_track = 16
|
||||
const max_channel = 16
|
||||
const max_note_number = 128
|
||||
const max_program_number = 128
|
||||
const drum_track_channel = 0x09
|
||||
const drum_track_bank = 128
|
||||
const ADSR = preload( "ADSR.tscn" )
|
||||
const SMF = preload( "SMF.gd" )
|
||||
const SoundFont = preload( "SoundFont.gd" )
|
||||
const Bank = preload( "Bank.gd" )
|
||||
|
||||
export var max_polyphony = 64
|
||||
export var file = ""
|
||||
export var playing = false
|
||||
export var channel_mute = [false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]
|
||||
export var play_speed = 1.0
|
||||
export var volume_db = -30
|
||||
export var key_shift = 0
|
||||
export var loop = false
|
||||
export var loop_start = 1.81
|
||||
export var soundfont = ""
|
||||
export var mix_target = AudioStreamPlayer.MIX_TARGET_STEREO
|
||||
export var bus = "Master"
|
||||
|
||||
var smf_data = null
|
||||
var tempo = 120 setget set_tempo
|
||||
var seconds_to_timebase = 2.3
|
||||
var position = 0
|
||||
var last_position = 0
|
||||
var track_status = null
|
||||
var channel_status = []
|
||||
var channel_volume_db = 20
|
||||
|
||||
var bank = null
|
||||
var audio_stream_players = []
|
||||
|
||||
var _used_program_numbers = []
|
||||
|
||||
signal changed_tempo( tempo )
|
||||
signal appeared_lyric( lyric )
|
||||
signal appeared_marker( marker )
|
||||
signal appeared_cue_point( cue_point )
|
||||
signal looped
|
||||
|
||||
func _ready( ):
|
||||
self._prepare_to_play( )
|
||||
if self.playing:
|
||||
self.play( )
|
||||
|
||||
"""
|
||||
初期化
|
||||
"""
|
||||
func _prepare_to_play( ):
|
||||
# ファイル読み込み
|
||||
if self.smf_data == null:
|
||||
var smf_reader = SMF.new( )
|
||||
self.smf_data = smf_reader.read_file( self.file )
|
||||
|
||||
self._init_track( )
|
||||
self._analyse_smf( )
|
||||
self._init_channel( )
|
||||
|
||||
# 楽器
|
||||
if self.bank == null:
|
||||
self.bank = Bank.new( )
|
||||
if self.soundfont != "":
|
||||
var sf_reader = SoundFont.new( )
|
||||
var sf2 = sf_reader.read_file( self.soundfont )
|
||||
self.bank.read_soundfont( sf2, self._used_program_numbers )
|
||||
|
||||
# 発音機
|
||||
if self.audio_stream_players.size( ) == 0:
|
||||
for i in range( self.max_polyphony ):
|
||||
var audio_stream_player = ADSR.instance( )
|
||||
audio_stream_player.mix_target = self.mix_target
|
||||
audio_stream_player.bus = self.bus
|
||||
self.add_child( audio_stream_player )
|
||||
self.audio_stream_players.append( audio_stream_player )
|
||||
else:
|
||||
for audio_stream_player in self.audio_stream_players:
|
||||
audio_stream_player.mix_target = self.mix_target
|
||||
audio_stream_player.bus = self.bus
|
||||
|
||||
"""
|
||||
トラック初期化
|
||||
"""
|
||||
class TrackSorter:
|
||||
static func sort(a, b):
|
||||
if a.time < b.time:
|
||||
return true
|
||||
elif b.time < a.time:
|
||||
return false
|
||||
else:
|
||||
if a.event.type == SMF.MIDIEventType.note_off:
|
||||
return true
|
||||
elif b.event.type == SMF.MIDIEventType.note_off:
|
||||
return false
|
||||
|
||||
func _init_track( ):
|
||||
self.track_status = {
|
||||
"events": [],
|
||||
"event_pointer": 0,
|
||||
}
|
||||
|
||||
for track in self.smf_data.tracks:
|
||||
self.track_status.events += track.events
|
||||
if 1 < self.smf_data.tracks.size( ):
|
||||
self.track_status.events.sort_custom( TrackSorter, "sort" )
|
||||
self.last_position = self.track_status.events[len(self.track_status.events)-1].time
|
||||
|
||||
"""
|
||||
SMF解析
|
||||
"""
|
||||
func _analyse_smf( ):
|
||||
var channels = []
|
||||
for i in range( max_channel ):
|
||||
channels.append({
|
||||
#"note_on": {},
|
||||
#"program_number": 0,
|
||||
"number": i,
|
||||
"bank": 0,
|
||||
})
|
||||
self._used_program_numbers = []
|
||||
|
||||
for event_chunk in self.track_status.events:
|
||||
var channel_number = event_chunk.channel_number
|
||||
var channel = channels[channel_number]
|
||||
var event = event_chunk.event
|
||||
|
||||
match event.type:
|
||||
#SMF.MIDIEventType.note_off:
|
||||
# channel.note_on.erase( event.note )
|
||||
#SMF.MIDIEventType.note_on:
|
||||
# channel.note_on[event.note] = true
|
||||
SMF.MIDIEventType.program_change:
|
||||
var program_number = event.number | ( channel.bank << 7 )
|
||||
# channel.program_number = program_number
|
||||
if not( event.number in self._used_program_numbers ):
|
||||
self._used_program_numbers.append( event.number )
|
||||
if not( program_number in self._used_program_numbers ):
|
||||
self._used_program_numbers.append( program_number )
|
||||
SMF.MIDIEventType.control_change:
|
||||
match event.number:
|
||||
SMF.control_number_bank_select_msb:
|
||||
if channel.number == drum_track_channel:
|
||||
channel.bank = drum_track_bank
|
||||
else:
|
||||
channel.bank = ( channel.bank & 0x7F ) | ( event.value << 7 )
|
||||
SMF.control_number_bank_select_lsb:
|
||||
if channel.number == drum_track_channel:
|
||||
channel.bank = drum_track_bank
|
||||
else:
|
||||
channel.bank = ( channel.bank & 0x3F80 ) | ( event.value & 0x7F )
|
||||
SMF.control_number_tkool_loop_point:
|
||||
self.loop_start = event_chunk.time
|
||||
|
||||
"""
|
||||
チャンネル初期化
|
||||
"""
|
||||
func _init_channel( ):
|
||||
self.channel_status = []
|
||||
for i in range( max_channel ):
|
||||
var drum_track = ( i == drum_track_channel )
|
||||
var bank = 0
|
||||
if drum_track:
|
||||
bank = self.drum_track_bank
|
||||
self.channel_status.append({
|
||||
"number": i,
|
||||
"note_on": {},
|
||||
"bank": bank,
|
||||
"program": 0,
|
||||
"volume": 1.0,
|
||||
"expression": 1.0,
|
||||
"pitch_bend": 0.0,
|
||||
"drum_track": drum_track,
|
||||
"pan": 0.5,
|
||||
})
|
||||
|
||||
"""
|
||||
再生
|
||||
@param from_position
|
||||
"""
|
||||
func play( from_position = 0 ):
|
||||
self.playing = true
|
||||
self.seek( from_position )
|
||||
|
||||
"""
|
||||
シーク
|
||||
"""
|
||||
func seek( to_position ):
|
||||
self._stop_all_notes( )
|
||||
self.position = to_position
|
||||
|
||||
var pointer = 0
|
||||
var length = len(self.track_status.events)
|
||||
while pointer < length:
|
||||
var event_chunk = self.track_status.events[pointer]
|
||||
if self.position < event_chunk.time:
|
||||
break
|
||||
pointer += 1
|
||||
self.track_status.event_pointer = pointer
|
||||
|
||||
"""
|
||||
停止
|
||||
"""
|
||||
func stop( ):
|
||||
self._stop_all_notes( )
|
||||
self.playing = false
|
||||
|
||||
"""
|
||||
テンポ設定
|
||||
"""
|
||||
func set_tempo( bpm ):
|
||||
tempo = bpm
|
||||
self.seconds_to_timebase = tempo / 60.0
|
||||
self.emit_signal( "changed_tempo", bpm )
|
||||
|
||||
"""
|
||||
全音を止める
|
||||
"""
|
||||
func _stop_all_notes( ):
|
||||
for audio_stream_player in self.audio_stream_players:
|
||||
audio_stream_player.stop( )
|
||||
|
||||
for channel in self.channel_status:
|
||||
channel.note_on = {}
|
||||
|
||||
"""
|
||||
毎フレーム処理
|
||||
"""
|
||||
func _process( delta ):
|
||||
if self.smf_data == null:
|
||||
return
|
||||
if not self.playing:
|
||||
return
|
||||
|
||||
self.position += self.smf_data.timebase * delta * self.seconds_to_timebase * self.play_speed
|
||||
self._process_track( )
|
||||
|
||||
"""
|
||||
トラック処理
|
||||
"""
|
||||
func _process_track( ):
|
||||
var track = self.track_status
|
||||
if track.events == null:
|
||||
return
|
||||
|
||||
var length = len(track.events)
|
||||
|
||||
if length <= track.event_pointer:
|
||||
if self.loop:
|
||||
self.seek( self.loop_start )
|
||||
self.emit_signal( "looped" )
|
||||
else:
|
||||
self.playing = false
|
||||
return
|
||||
|
||||
for channel in self.channel_status:
|
||||
for key_number in channel.note_on.keys( ):
|
||||
var note_on = channel.note_on[key_number]
|
||||
if not note_on.playing:
|
||||
channel.note_on.erase( key_number )
|
||||
|
||||
while track.event_pointer < length:
|
||||
var event_chunk = track.events[track.event_pointer]
|
||||
if self.position < event_chunk.time:
|
||||
break
|
||||
track.event_pointer += 1
|
||||
|
||||
var channel = self.channel_status[event_chunk.channel_number]
|
||||
var event = event_chunk.event
|
||||
|
||||
match event.type:
|
||||
SMF.MIDIEventType.note_off:
|
||||
self._process_track_event_note_off( channel, event )
|
||||
SMF.MIDIEventType.note_on:
|
||||
self._process_track_event_note_on( channel, event )
|
||||
SMF.MIDIEventType.program_change:
|
||||
channel.program = event.number
|
||||
SMF.MIDIEventType.control_change:
|
||||
self._process_track_event_control_change( channel, event )
|
||||
SMF.MIDIEventType.pitch_bend:
|
||||
channel.pitch_bend = event.value / 8192.0 - 1.0
|
||||
self._update_pitch_bend_note( channel )
|
||||
SMF.MIDIEventType.system_event:
|
||||
self._process_track_system_event( channel, event )
|
||||
_:
|
||||
# 無視
|
||||
pass
|
||||
|
||||
func _process_track_event_note_off( channel, event ):
|
||||
var key_number = event.note + self.key_shift
|
||||
if channel.note_on.has( key_number ):
|
||||
var note_player = channel.note_on[key_number]
|
||||
if note_player != null:
|
||||
note_player.start_release( )
|
||||
channel.note_on.erase( key_number )
|
||||
|
||||
func _process_track_event_note_on( channel, event ):
|
||||
if not self.channel_mute[channel.number]:
|
||||
var key_number = event.note + self.key_shift
|
||||
var note_volume = channel.volume * channel.expression * ( event.velocity / 127.0 )
|
||||
var volume_db = note_volume * self.channel_volume_db - self.channel_volume_db + self.volume_db
|
||||
var preset = self.bank.get_preset( channel.program, channel.bank )
|
||||
var instrument = preset.instruments[key_number]
|
||||
|
||||
if instrument != null:
|
||||
if channel.note_on.has( key_number ):
|
||||
channel.note_on[key_number].start_release( )
|
||||
|
||||
var note_player = self._get_idle_player( channel.program )
|
||||
if note_player != null:
|
||||
note_player.velocity = event.velocity
|
||||
note_player.maximum_volume_db = volume_db
|
||||
note_player.pitch_bend = channel.pitch_bend
|
||||
note_player.set_instrument( instrument )
|
||||
note_player.play( )
|
||||
if not channel.drum_track:
|
||||
channel.note_on[key_number] = note_player
|
||||
|
||||
func _process_track_event_control_change( channel, event ):
|
||||
match event.number:
|
||||
SMF.control_number_volume:
|
||||
channel.volume = event.value / 127.0
|
||||
self._update_volume_note( channel )
|
||||
SMF.control_number_expression:
|
||||
channel.expression = event.value / 127.0
|
||||
self._update_volume_note( channel )
|
||||
SMF.control_number_pan:
|
||||
channel.pan = event.value / 127.0
|
||||
SMF.control_number_bank_select_msb:
|
||||
if channel.drum_track:
|
||||
channel.bank = self.drum_track_bank
|
||||
else:
|
||||
channel.bank = ( channel.bank & 0x7F ) | ( event.value << 7 )
|
||||
SMF.control_number_bank_select_lsb:
|
||||
if channel.drum_track:
|
||||
channel.bank = self.drum_track_bank
|
||||
else:
|
||||
channel.bank = ( channel.bank & 0x3F80 ) | ( event.value & 0x7F )
|
||||
_:
|
||||
# 無視
|
||||
pass
|
||||
|
||||
func _process_track_system_event( channel, event ):
|
||||
match event.args.type:
|
||||
SMF.MIDISystemEventType.set_tempo:
|
||||
self.tempo = 60000000.0 / event.args.bpm
|
||||
SMF.MIDISystemEventType.lyric:
|
||||
self.emit_signal( "appeared_lyric", event.args.text )
|
||||
SMF.MIDISystemEventType.marker:
|
||||
self.emit_signal( "appeared_marker", event.args.text )
|
||||
SMF.MIDISystemEventType.cue_point:
|
||||
self.emit_signal( "appeared_cue_point", event.args.text )
|
||||
_:
|
||||
# 無視
|
||||
pass
|
||||
|
||||
func _get_idle_player( program ):
|
||||
var stopped_audio_stream_player = null
|
||||
var minimum_volume = 100.0
|
||||
var oldest_audio_stream_player = null
|
||||
var oldest = 0.0
|
||||
|
||||
for audio_stream_player in self.audio_stream_players:
|
||||
if not audio_stream_player.playing:
|
||||
return audio_stream_player
|
||||
if audio_stream_player.releasing and audio_stream_player.current_volume < minimum_volume:
|
||||
stopped_audio_stream_player = audio_stream_player
|
||||
minimum_volume = audio_stream_player.current_volume
|
||||
if oldest < audio_stream_player.using_timer:
|
||||
oldest_audio_stream_player = audio_stream_player
|
||||
oldest = audio_stream_player.using_timer
|
||||
|
||||
if stopped_audio_stream_player != null:
|
||||
return stopped_audio_stream_player
|
||||
|
||||
return oldest_audio_stream_player
|
||||
|
||||
func _update_volume_note( channel ):
|
||||
for note in channel.note_on.values( ):
|
||||
var note_volume = channel.volume * channel.expression * ( note.velocity / 127.0 )
|
||||
var volume_db = note_volume * self.channel_volume_db - self.channel_volume_db + self.volume_db
|
||||
note.maximum_volume_db = volume_db
|
||||
|
||||
func _update_pitch_bend_note( channel ):
|
||||
for note in channel.note_on.values( ):
|
||||
note.set_pitch_bend( channel.pitch_bend )
|
||||
|
||||
"""
|
||||
現在発音中の音色数を返す
|
||||
"""
|
||||
func get_now_playing_polyphony( ):
|
||||
var polyphony = 0
|
||||
for audio_stream_player in self.audio_stream_players:
|
||||
if audio_stream_player.playing:
|
||||
polyphony += 1
|
||||
return polyphony
|
||||
30
source/midi/MidiPlayer.tscn
Normal file
@@ -0,0 +1,30 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://midi/MidiPlayer.gd" type="Script" id=1]
|
||||
|
||||
[node name="MidiPlayer" type="Node" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
max_polyphony = 64
|
||||
file = ""
|
||||
playing = false
|
||||
channel_mute = [ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ]
|
||||
play_speed = 1.0
|
||||
volume_db = -30
|
||||
key_shift = 0
|
||||
loop = false
|
||||
loop_start = 0
|
||||
soundfont = ""
|
||||
mix_target = 0
|
||||
bus = "Master"
|
||||
|
||||
[node name="Default" type="AudioStreamPlayer" parent="." index="0"]
|
||||
|
||||
stream = null
|
||||
volume_db = 0.0
|
||||
pitch_scale = 1.0
|
||||
autoplay = false
|
||||
mix_target = 0
|
||||
bus = "Master"
|
||||
|
||||
|
||||
634
source/midi/SMF.gd
Normal file
@@ -0,0 +1,634 @@
|
||||
"""
|
||||
SMF reader/writer by Yui Kinomoto @arlez80
|
||||
"""
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 定数
|
||||
|
||||
# Control Numbers
|
||||
const control_number_bank_select_msb = 0x00
|
||||
const control_number_modulation = 0x01
|
||||
const control_number_breath_controller = 0x02
|
||||
const control_number_foot_controller = 0x04
|
||||
const control_number_portamento_time = 0x05
|
||||
const control_number_data_entry = 0x06
|
||||
const control_number_volume = 0x07
|
||||
const control_number_balance = 0x08
|
||||
const control_number_pan = 0x0A
|
||||
const control_number_expression = 0x0B
|
||||
|
||||
const control_number_bank_select_lsb = 0x20
|
||||
const control_number_modulation_lsb = 0x21
|
||||
const control_number_breath_controller_lsb = 0x22
|
||||
const control_number_foot_controller_lsb = 0x24
|
||||
const control_number_portamento_time_lsb = 0x25
|
||||
const control_number_data_entry_lsb = 0x26
|
||||
const control_number_channel_volume_lsb = 0x27
|
||||
const control_number_calance_lsb = 0x28
|
||||
const control_number_pan_lsb = 0x2A
|
||||
const control_number_expression_lsb = 0x2B
|
||||
|
||||
const control_number_hold = 0x40
|
||||
const control_number_portament = 0x41
|
||||
const control_number_sostenuto = 0x42
|
||||
const control_number_soft_pedal = 0x43
|
||||
const control_number_legato_foot_switch = 0x44
|
||||
const control_number_freeze = 0x45
|
||||
const control_number_sound_variation = 0x46
|
||||
const control_number_timbre = 0x47
|
||||
const control_number_release_time = 0x48
|
||||
const control_number_attack_time = 0x49
|
||||
const control_number_brightness = 0x4A
|
||||
const control_number_vibrato_rate = 0x4B
|
||||
const control_number_vibrato_depth = 0x4C
|
||||
const control_number_vibrato_delay = 0x4D
|
||||
|
||||
const control_number_nrpn_lsb = 0x62
|
||||
const control_number_nrpn_msb = 0x63
|
||||
const control_number_rpn_lsb = 0x64
|
||||
const control_number_rpn_msb = 0x65
|
||||
const control_number_tkool_loop_point = 0x6F # CC111
|
||||
|
||||
# Manufacture ID
|
||||
const manufacture_id_universal_nopn_realtime_sys_ex = 0x7E
|
||||
const manufacture_id_universal_realtime_sys_ex = 0x7F
|
||||
const manufacture_id_kawai_musical_instruments_mfg_co_ltd = 0x40
|
||||
const manufacture_id_roland_corporation = 0x41
|
||||
const manufacture_id_korg_inc = 0x42
|
||||
const manufacture_id_yamaha_corporation = 0x43
|
||||
const manufacture_id_casio_computer_co_ltd = 0x44
|
||||
const manufacture_id_kamiya_studio_co_ltd = 0x46
|
||||
const manufacture_id_akai_electric_co_ltd = 0x47
|
||||
|
||||
# Enums
|
||||
enum MIDIEventType {
|
||||
note_off, # 8*
|
||||
note_on, # 9*
|
||||
polyphonic_key_pressure, # A*
|
||||
control_change, # B*
|
||||
program_change, # C*
|
||||
channel_pressure, # D*
|
||||
pitch_bend, # E*
|
||||
system_event, # F*
|
||||
}
|
||||
|
||||
enum MIDISystemEventType {
|
||||
sys_ex,
|
||||
divided_sys_ex,
|
||||
text_event, # 01
|
||||
copyright, # 02
|
||||
track_name, # 03
|
||||
instrument_name, # 04
|
||||
lyric, # 05
|
||||
marker, # 06
|
||||
cue_point, # 07
|
||||
midi_channel_prefix, # 20
|
||||
end_of_track, # 2F
|
||||
|
||||
set_tempo, # 51
|
||||
|
||||
smpte_offset, # 54
|
||||
beat, # 58
|
||||
key, # 59
|
||||
|
||||
unknown,
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 読み込み : Reader
|
||||
|
||||
var last_event_type
|
||||
|
||||
"""
|
||||
ファイルから読み込み
|
||||
@param path File path
|
||||
@return smf or null(read error)
|
||||
"""
|
||||
func read_file( path ):
|
||||
var f = File.new( )
|
||||
|
||||
if not f.file_exists( path ):
|
||||
print( "file %s is not found" % path )
|
||||
breakpoint
|
||||
|
||||
f.open( path, f.READ )
|
||||
var stream = StreamPeerBuffer.new( )
|
||||
stream.set_data_array( f.get_buffer( f.get_len( ) ) )
|
||||
stream.big_endian = true
|
||||
f.close( )
|
||||
|
||||
return self._read( stream )
|
||||
|
||||
"""
|
||||
配列から読み込み
|
||||
@param data PoolByteArray
|
||||
@return smf or null(read error)
|
||||
"""
|
||||
func read_data( data ):
|
||||
var stream = StreamPeerBuffer.new( )
|
||||
stream.set_data_array( data )
|
||||
stream.big_endian = true
|
||||
return self._read( stream )
|
||||
|
||||
"""
|
||||
読み込み
|
||||
@param input
|
||||
@return smf
|
||||
"""
|
||||
func _read( input ):
|
||||
var header = self._read_chunk_data( input )
|
||||
if header.id != "MThd" and header.size != 6:
|
||||
print( "expected MThd header" )
|
||||
return null
|
||||
|
||||
var format_type = header.stream.get_u16( )
|
||||
var track_count = header.stream.get_u16( )
|
||||
var timebase = header.stream.get_u16( )
|
||||
|
||||
var tracks = []
|
||||
for i in range( 0, track_count ):
|
||||
var track = self._read_track( input, i )
|
||||
if track == null:
|
||||
return null
|
||||
tracks.append( track )
|
||||
|
||||
return {
|
||||
"format_type": format_type,
|
||||
"track_count": track_count,
|
||||
"timebase": timebase,
|
||||
"tracks": tracks,
|
||||
}
|
||||
|
||||
"""
|
||||
トラックの読み込み
|
||||
@param input
|
||||
@param track_number トラックナンバー
|
||||
@return track data or null(read error)
|
||||
"""
|
||||
func _read_track( input, track_number ):
|
||||
var track_chunk = self._read_chunk_data( input )
|
||||
if track_chunk.id != "MTrk":
|
||||
print( "Unknown chunk: " + track_chunk.id )
|
||||
return null
|
||||
|
||||
var stream = track_chunk.stream
|
||||
var time = 0
|
||||
var events = []
|
||||
|
||||
while 0 < stream.get_available_bytes( ):
|
||||
var delta_time = self._read_variable_int( stream )
|
||||
time += delta_time
|
||||
var event_type_byte = stream.get_u8( )
|
||||
|
||||
var event
|
||||
if self._is_system_event( event_type_byte ):
|
||||
var args = self._read_system_event( stream, event_type_byte )
|
||||
if args == null: return null
|
||||
event = {
|
||||
"type": MIDIEventType.system_event,
|
||||
"args": args
|
||||
}
|
||||
else:
|
||||
event = self._read_event( stream, event_type_byte )
|
||||
if event == null: return null
|
||||
|
||||
if ( event_type_byte & 0x80 ) == 0:
|
||||
event_type_byte = self.last_event_type
|
||||
|
||||
events.append({
|
||||
"time": time,
|
||||
"channel_number": event_type_byte & 0x0f,
|
||||
"event": event,
|
||||
})
|
||||
|
||||
return {
|
||||
"track_number": track_number,
|
||||
"events": events,
|
||||
}
|
||||
|
||||
"""
|
||||
システムイベントか否かを返す
|
||||
@param b event type
|
||||
@return システムイベントならtrueを返す
|
||||
"""
|
||||
func _is_system_event( b ):
|
||||
return ( b & 0xf0 ) == 0xf0
|
||||
|
||||
"""
|
||||
システムイベントの読み込み
|
||||
"""
|
||||
func _read_system_event( stream, event_type_byte ):
|
||||
if event_type_byte == 0xff:
|
||||
var meta_type = stream.get_u8( )
|
||||
var size = self._read_variable_int( stream )
|
||||
|
||||
match meta_type:
|
||||
0x01:
|
||||
return { "type": MIDISystemEventType.text_event, "text": self._read_string( stream, size ) }
|
||||
0x02:
|
||||
return { "type": MIDISystemEventType.copyright, "text": self._read_string( stream, size ) }
|
||||
0x03:
|
||||
return { "type": MIDISystemEventType.track_name, "text": self._read_string( stream, size ) }
|
||||
0x04:
|
||||
return { "type": MIDISystemEventType.instrument_name, "text": self._read_string( stream, size ) }
|
||||
0x05:
|
||||
return { "type": MIDISystemEventType.lyric, "text": self._read_string( stream, size ) }
|
||||
0x06:
|
||||
return { "type": MIDISystemEventType.marker, "text": self._read_string( stream, size ) }
|
||||
0x07:
|
||||
return { "type": MIDISystemEventType.cue_point, "text": self._read_string( stream, size ) }
|
||||
0x20:
|
||||
if size != 1:
|
||||
print( "MIDI Channel Prefix length is not 1" )
|
||||
return null
|
||||
return { "type": MIDISystemEventType.midi_channel_prefix, "prefix": stream.get_u8( ) }
|
||||
0x2F:
|
||||
if size != 0:
|
||||
print( "End of track with unknown data" )
|
||||
return null
|
||||
return { "type": MIDISystemEventType.end_of_track }
|
||||
0x51:
|
||||
if size != 3:
|
||||
print( "Tempo length is not 3" )
|
||||
return null
|
||||
# beat per microseconds
|
||||
var bpm = stream.get_u8( ) << 16
|
||||
bpm |= stream.get_u8( ) << 8
|
||||
bpm |= stream.get_u8( )
|
||||
return { "type": MIDISystemEventType.set_tempo, "bpm": bpm }
|
||||
0x54:
|
||||
if size != 5:
|
||||
print( "SMPTE length is not 5" )
|
||||
return null
|
||||
var hr = stream.get_u8( )
|
||||
var mm = stream.get_u8( )
|
||||
var se = stream.get_u8( )
|
||||
var fr = stream.get_u8( )
|
||||
var ff = stream.get_u8( )
|
||||
return {
|
||||
"type": MIDISystemEventType.smpte_offset,
|
||||
"hr": hr,
|
||||
"mm": mm,
|
||||
"se": se,
|
||||
"fr": fr,
|
||||
"ff": ff,
|
||||
}
|
||||
0x58:
|
||||
if size != 4:
|
||||
print( "Beat length is not 4" )
|
||||
return null
|
||||
var numerator = stream.get_u8( )
|
||||
var denominator = stream.get_u8( )
|
||||
var clock = stream.get_u8( )
|
||||
var beat32 = stream.get_u8( )
|
||||
return {
|
||||
"type": MIDISystemEventType.beat,
|
||||
"numerator": numerator,
|
||||
"denominator": denominator,
|
||||
"clock": clock,
|
||||
"beat32": beat32,
|
||||
}
|
||||
0x59:
|
||||
if size != 2:
|
||||
print( "Key length is not 2" )
|
||||
return null
|
||||
var sf = stream.get_u8( )
|
||||
var minor = stream.get_u8( ) == 1
|
||||
return {
|
||||
"type": MIDISystemEventType.key,
|
||||
"sf": sf,
|
||||
"minor": minor,
|
||||
}
|
||||
_:
|
||||
return {
|
||||
"type": MIDISystemEventType.unknown,
|
||||
"meta_type": meta_type,
|
||||
"data": stream.get_partial_data( size )[1],
|
||||
}
|
||||
elif event_type_byte == 0xf0:
|
||||
var size = self._read_variable_int( stream )
|
||||
return {
|
||||
"type": MIDISystemEventType.sys_ex,
|
||||
"data": stream.get_partial_data( size )[1],
|
||||
}
|
||||
elif event_type_byte == 0xf7:
|
||||
var size = self._read_variable_int( stream )
|
||||
return {
|
||||
"type": MIDISystemEventType.divided_sys_ex,
|
||||
"data": stream.get_partial_data( size )[1],
|
||||
}
|
||||
|
||||
print( "Unknown system event type: %x" % event_type_byte )
|
||||
return null
|
||||
|
||||
"""
|
||||
通常のイベント読み込み
|
||||
@param stream
|
||||
@param event_type_byte
|
||||
@return MIDIEvent
|
||||
"""
|
||||
func _read_event( stream, event_type_byte ):
|
||||
var param = 0
|
||||
|
||||
if ( event_type_byte & 0x80 ) == 0:
|
||||
# running status
|
||||
param = event_type_byte
|
||||
event_type_byte = self.last_event_type
|
||||
else:
|
||||
param = stream.get_u8( )
|
||||
self.last_event_type = event_type_byte
|
||||
|
||||
var event_type = event_type_byte & 0xf0
|
||||
|
||||
match event_type:
|
||||
0x80:
|
||||
return {
|
||||
"type": MIDIEventType.note_off,
|
||||
"note": param,
|
||||
"velocity": stream.get_u8( ),
|
||||
}
|
||||
0x90:
|
||||
var velocity = stream.get_u8( )
|
||||
if velocity == 0:
|
||||
# velocity0のnote_onはnote_off扱いにする
|
||||
return {
|
||||
"type": MIDIEventType.note_off,
|
||||
"note": param,
|
||||
"velocity": velocity,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"type": MIDIEventType.note_on,
|
||||
"note": param,
|
||||
"velocity": velocity,
|
||||
}
|
||||
0xA0:
|
||||
return {
|
||||
"type": MIDIEventType.polyphonic_key_pressure,
|
||||
"note": param,
|
||||
"value": stream.get_u8( ),
|
||||
}
|
||||
0xB0:
|
||||
return {
|
||||
"type": MIDIEventType.control_change,
|
||||
"number": param,
|
||||
"value": stream.get_u8( ),
|
||||
}
|
||||
0xC0:
|
||||
return {
|
||||
"type": MIDIEventType.program_change,
|
||||
"number": param,
|
||||
}
|
||||
0xD0:
|
||||
return {
|
||||
"type": MIDIEventType.channel_pressure,
|
||||
"value": param,
|
||||
}
|
||||
0xE0:
|
||||
return {
|
||||
"type": MIDIEventType.pitch_bend,
|
||||
"value": param | ( stream.get_u8( ) << 7 ),
|
||||
}
|
||||
|
||||
print( "unknown event type: %d" % event_type_byte )
|
||||
return null
|
||||
|
||||
"""
|
||||
可変長数値の読み込み
|
||||
@param stream
|
||||
@return 数値
|
||||
"""
|
||||
func _read_variable_int( stream ):
|
||||
var result = 0
|
||||
|
||||
while true:
|
||||
var c = stream.get_u8( )
|
||||
if ( c & 0x80 ) != 0:
|
||||
result |= c & 0x7f
|
||||
result <<= 7
|
||||
else:
|
||||
result |= c
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
"""
|
||||
チャンクデータの読み込み
|
||||
@param stream Stream
|
||||
@return chunk data
|
||||
"""
|
||||
func _read_chunk_data( stream ):
|
||||
var id = self._read_string( stream, 4 )
|
||||
var size = stream.get_32( )
|
||||
var new_stream = StreamPeerBuffer.new( )
|
||||
new_stream.set_data_array( stream.get_partial_data( size )[1] )
|
||||
new_stream.big_endian = true
|
||||
|
||||
return {
|
||||
"id": id,
|
||||
"size": size,
|
||||
"stream": new_stream
|
||||
}
|
||||
|
||||
"""
|
||||
文字列の読み込み
|
||||
@param stream Stream
|
||||
@param size string size
|
||||
@return string
|
||||
"""
|
||||
func _read_string( stream, size ):
|
||||
return stream.get_partial_data( size )[1].get_string_from_ascii( )
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 書き込み: Writer
|
||||
|
||||
"""
|
||||
書き込む
|
||||
@param smf SMF structure
|
||||
@return PoolByteArray
|
||||
"""
|
||||
func write( smf ):
|
||||
var stream = StreamPeerBuffer.new( )
|
||||
stream.big_endian = true
|
||||
|
||||
stream.put_utf8_string( "MThd".to_ascii( ) )
|
||||
stream.put_u32( 6 )
|
||||
stream.put_u16( smf.format_type )
|
||||
stream.put_u16( len( smf.tracks ) )
|
||||
stream.put_u16( smf.timebase )
|
||||
|
||||
for t in smf.tracks:
|
||||
self._write_track( stream, t )
|
||||
|
||||
return stream.get_partial_data( stream.get_available_bytes( ) )[1]
|
||||
|
||||
"""
|
||||
トラックデータソート用
|
||||
"""
|
||||
class TrackEventSorter:
|
||||
static func sort( a, b ):
|
||||
if a.time < b.time:
|
||||
return true
|
||||
return false
|
||||
|
||||
"""
|
||||
可変長数字を書き込む
|
||||
@param stream
|
||||
@param i
|
||||
"""
|
||||
func _write_variable_int( stream, i ):
|
||||
while true:
|
||||
var v = i & 0x7f
|
||||
i >>= 7
|
||||
if i != 0:
|
||||
stream.put_u8( v | 0x80 )
|
||||
else:
|
||||
stream.put_u8( v )
|
||||
break
|
||||
|
||||
"""
|
||||
トラックデータを書き込む
|
||||
@param stream
|
||||
@param track
|
||||
"""
|
||||
func _write_track( stream, track ):
|
||||
var events = track.events.duplicate( )
|
||||
events.sort_custom( TrackEventSorter, "sort" )
|
||||
|
||||
var buf = StreamPeerBuffer.new( )
|
||||
buf.big_endian = true
|
||||
var time = 0
|
||||
|
||||
for e in events:
|
||||
self._write_variable_int( buf, e.time - time )
|
||||
time = e.time
|
||||
match e.type:
|
||||
MIDIEventType.note_off:
|
||||
buf.put_u8( 0x80 | e.channel_number )
|
||||
buf.put_u8( e.note )
|
||||
buf.put_u8( e.velocity )
|
||||
MIDIEventType.note_on:
|
||||
buf.put_u8( 0x90 | e.channel_number )
|
||||
buf.put_u8( e.note )
|
||||
buf.put_u8( e.velocity )
|
||||
MIDIEventType.polyphonic_key_pressure:
|
||||
buf.put_u8( 0xA0 | e.channel_number )
|
||||
buf.put_u8( e.note )
|
||||
buf.put_u8( e.value )
|
||||
MIDIEventType.control_change:
|
||||
buf.put_u8( 0xB0 | e.channel_number )
|
||||
buf.put_u8( e.number )
|
||||
buf.put_u8( e.value )
|
||||
MIDIEventType.program_change:
|
||||
buf.put_u8( 0xC0 | e.channel_number )
|
||||
buf.put_u8( e.number )
|
||||
MIDIEventType.channel_pressure:
|
||||
buf.put_u8( 0xD0 | e.channel_number )
|
||||
buf.put_u8( e.value )
|
||||
MIDIEventType.pitch_bend:
|
||||
buf.put_u8( 0xE0 | e.channel_number )
|
||||
buf.put_u8( e.value & 0x7f )
|
||||
buf.put_u8( ( e.value >> 7 ) & 0x7f )
|
||||
MIDIEventType.system_event:
|
||||
self._write_system_event( buf, e )
|
||||
|
||||
var track_size = buf.get_available_bytes( )
|
||||
stream.put_utf8_string( "MTrk".to_ascii( ) )
|
||||
stream.put_u32( track_size )
|
||||
stream.put_data( buf.get_partial_data( track_size )[1] )
|
||||
|
||||
"""
|
||||
システムイベント書き込み
|
||||
@param stream
|
||||
@param event
|
||||
"""
|
||||
func _write_system_event( stream, event ):
|
||||
match event.type:
|
||||
MIDISystemEventType.sys_ex:
|
||||
stream.put_u8( 0xF0 )
|
||||
self._write_variable_int( stream, len( event.data ) )
|
||||
stream.put_data( event.data )
|
||||
MIDISystemEventType.divided_sys_ex:
|
||||
stream.put_u8( 0xF7 )
|
||||
self._write_variable_int( stream, len( event.data ) )
|
||||
stream.put_data( event.data )
|
||||
MIDISystemEventType.text_event:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x01 )
|
||||
self._write_variable_int( stream, len( event.text ) )
|
||||
stream.put_data( event.text.to_ascii( ) )
|
||||
MIDISystemEventType.copyright:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x02 )
|
||||
self._write_variable_int( stream, len( event.text ) )
|
||||
stream.put_data( event.text.to_ascii( ) )
|
||||
MIDISystemEventType.track_name:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x03 )
|
||||
self._write_variable_int( stream, len( event.text ) )
|
||||
stream.put_data( event.text.to_ascii( ) )
|
||||
MIDISystemEventType.instrument_name:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x04 )
|
||||
self._write_variable_int( stream, len( event.text ) )
|
||||
stream.put_data( event.text.to_ascii( ) )
|
||||
MIDISystemEventType.lyric:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x05 )
|
||||
self._write_variable_int( stream, len( event.text ) )
|
||||
stream.put_data( event.text.to_ascii( ) )
|
||||
MIDISystemEventType.marker:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x06 )
|
||||
self._write_variable_int( stream, len( event.text ) )
|
||||
stream.put_data( event.text.to_ascii( ) )
|
||||
MIDISystemEventType.cue_point:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x07 )
|
||||
self._write_variable_int( stream, len( event.text ) )
|
||||
stream.put_data( event.text.to_ascii( ) )
|
||||
|
||||
MIDISystemEventType.midi_channel_prefix:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x20 )
|
||||
stream.put_u8( 0x01 )
|
||||
stream.put_u8( event.prefix )
|
||||
MIDISystemEventType.end_of_track:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x2F )
|
||||
stream.put_u8( 0x00 )
|
||||
MIDISystemEventType.set_tempo:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x51 )
|
||||
stream.put_u8( 0x03 )
|
||||
stream.put_u8( ( event.bpm >> 16 ) & 0xFF )
|
||||
stream.put_u8( ( event.bpm >> 8 ) & 0xFF )
|
||||
stream.put_u8( event.bpm & 0xFF )
|
||||
MIDISystemEventType.smpte_offset:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x54 )
|
||||
stream.put_u8( 0x05 )
|
||||
stream.put_u8( event.hr )
|
||||
stream.put_u8( event.mm )
|
||||
stream.put_u8( event.se )
|
||||
stream.put_u8( event.fr )
|
||||
stream.put_u8( event.ff )
|
||||
MIDISystemEventType.beat:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x58 )
|
||||
stream.put_u8( 0x04 )
|
||||
stream.put_u8( event.numerator )
|
||||
stream.put_u8( event.denominator )
|
||||
stream.put_u8( event.clock )
|
||||
stream.put_u8( event.beat32 )
|
||||
MIDISystemEventType.key:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( 0x59 )
|
||||
stream.put_u8( 0x02 )
|
||||
stream.put_u8( event.sf )
|
||||
stream.put_u8( 1 if event.minor else 0 )
|
||||
MIDISystemEventType.unknown:
|
||||
stream.put_u8( 0xFF )
|
||||
stream.put_u8( event.meta_type )
|
||||
stream.put_u8( len( event.data ) )
|
||||
stream.put_data( event.data )
|
||||
493
source/midi/SoundFont.gd
Normal file
@@ -0,0 +1,493 @@
|
||||
"""
|
||||
SoundFont Reader by Yui Kinomoto @arlez80
|
||||
"""
|
||||
|
||||
"""
|
||||
SampleLink
|
||||
"""
|
||||
const sample_link_mono_sample = 1
|
||||
const sample_link_right_sample = 2
|
||||
const sample_link_left_sample = 4
|
||||
const sample_link_linked_sample = 8
|
||||
const sample_link_rom_mono_sample = 0x8001
|
||||
const sample_link_rom_right_sample = 0x8002
|
||||
const sample_link_rom_left_sample = 0x8004
|
||||
const sample_link_rom_linked_sample = 0x8008
|
||||
|
||||
"""
|
||||
GenerateOperator
|
||||
"""
|
||||
const gen_oper_start_addrs_offset = 0
|
||||
const gen_oper_end_addrs_offset = 1
|
||||
const gen_oper_startloop_addrs_offset = 2
|
||||
const gen_oper_endloop_addrs_offset = 3
|
||||
const gen_oper_start_addrs_coarse_offset = 4
|
||||
const gen_oper_mod_lfo_to_pitch = 5
|
||||
const gen_oper_vib_lfo_to_pitch = 6
|
||||
const gen_oper_mod_env_to_pitch = 7
|
||||
const gen_oper_initial_filter_fc = 8
|
||||
const gen_oper_initial_filter_q = 9
|
||||
const gen_oper_mod_lfo_to_filter_fc = 10
|
||||
const gen_oper_mod_env_to_filter_fc = 11
|
||||
const gen_oper_end_addrs_coarse_offset = 12
|
||||
const gen_oper_mod_lfo_to_volume = 13
|
||||
const gen_oper_unused1 = 14
|
||||
const gen_oper_chorus_effects_send = 15
|
||||
const gen_oper_reverb_effects_send = 16
|
||||
const gen_oper_pan = 17
|
||||
const gen_oper_unused2 = 18
|
||||
const gen_oper_unused3 = 19
|
||||
const gen_oper_unused4 = 20
|
||||
const gen_oper_delay_mod_lfo = 21
|
||||
const gen_oper_freq_mod_lfo = 22
|
||||
const gen_oper_delay_vib_lfo = 23
|
||||
const gen_oper_freq_vib_lfo = 24
|
||||
const gen_oper_delay_mod_env = 25
|
||||
const gen_oper_attack_mod_env = 26
|
||||
const gen_oper_hold_mod_env = 27
|
||||
const gen_oper_decay_mod_env = 28
|
||||
const gen_oper_sustain_mod_env = 29
|
||||
const gen_oper_release_mod_env = 30
|
||||
const gen_oper_keynum_to_mod_env_hold = 31
|
||||
const gen_oper_keynum_to_mod_env_decay = 32
|
||||
const gen_oper_delay_vol_env = 33
|
||||
const gen_oper_attack_vol_env = 34
|
||||
const gen_oper_hold_vol_env = 35
|
||||
const gen_oper_decay_vol_env = 36
|
||||
const gen_oper_sustain_vol_env = 37
|
||||
const gen_oper_release_vol_env = 38
|
||||
const gen_oper_keynum_to_vol_env_hold = 39
|
||||
const gen_oper_keynum_to_vol_env_decay = 40
|
||||
const gen_oper_instrument = 41
|
||||
const gen_oper_reserved1 = 42
|
||||
const gen_oper_key_range = 43
|
||||
const gen_oper_vel_range = 44
|
||||
const gen_oper_startloop_addrs_coarse_offset = 45
|
||||
const gen_oper_keynum = 46
|
||||
const gen_oper_velocity = 47
|
||||
const gen_oper_initial_attenuation = 48
|
||||
const gen_oper_reserved2 = 49
|
||||
const gen_oper_endloop_addrs_coarse_offset = 50
|
||||
const gen_oper_coarse_tune = 51
|
||||
const gen_oper_fine_tune = 52
|
||||
const gen_oper_sample_id = 53
|
||||
const gen_oper_sample_modes = 54
|
||||
const gen_oper_reserved3 = 55
|
||||
const gen_oper_scale_tuning = 56
|
||||
const gen_oper_exclusive_class = 57
|
||||
const gen_oper_overriding_root_key = 58
|
||||
const gen_oper_unused5 = 59
|
||||
const gen_oper_end_oper = 60
|
||||
|
||||
"""
|
||||
SampleMode
|
||||
"""
|
||||
const sample_mode_no_loop = 0
|
||||
const sample_mode_loop_continuously = 1
|
||||
const sample_mode_unused_no_loop = 2
|
||||
const sample_mode_loop_ends_by_key_depression = 3
|
||||
|
||||
"""
|
||||
ファイルから読み込み
|
||||
@param path File path
|
||||
@return smf
|
||||
"""
|
||||
func read_file( path ):
|
||||
var f = File.new( )
|
||||
|
||||
if not f.file_exists( path ):
|
||||
print( "file %s is not found" % path )
|
||||
breakpoint
|
||||
|
||||
f.open( path, f.READ )
|
||||
var stream = StreamPeerBuffer.new( )
|
||||
stream.set_data_array( f.get_buffer( f.get_len( ) ) )
|
||||
stream.big_endian = false
|
||||
f.close( )
|
||||
|
||||
return self._read( stream )
|
||||
|
||||
"""
|
||||
配列から読み込み
|
||||
@param data PoolByteArray
|
||||
@return smf
|
||||
"""
|
||||
func read_data( data ):
|
||||
var stream = StreamPeerBuffer.new( )
|
||||
stream.set_data_array( data )
|
||||
stream.big_endian = false
|
||||
return self._read( stream )
|
||||
|
||||
"""
|
||||
読み込み
|
||||
@param input
|
||||
@return SoundFont
|
||||
"""
|
||||
func _read( input ):
|
||||
self._check_chunk( input, "RIFF" )
|
||||
self._check_header( input, "sfbk" )
|
||||
|
||||
var info = self._read_info( input )
|
||||
var sdta = self._read_sdta( input )
|
||||
var pdta = self._read_pdta( input )
|
||||
|
||||
return {
|
||||
"info": info,
|
||||
"sdta": sdta,
|
||||
"pdta": pdta,
|
||||
}
|
||||
|
||||
"""
|
||||
チャンクチェック
|
||||
@param input
|
||||
@param hdr
|
||||
"""
|
||||
func _check_chunk( input, hdr ):
|
||||
self._check_header( input, hdr )
|
||||
input.get_32( )
|
||||
|
||||
"""
|
||||
ヘッダーチェック
|
||||
@param input
|
||||
@param hdr
|
||||
"""
|
||||
func _check_header( input, hdr ):
|
||||
var chk = input.get_string( 4 )
|
||||
if hdr != chk:
|
||||
print( "Doesn't exist " + hdr + " header" )
|
||||
breakpoint
|
||||
|
||||
"""
|
||||
チャンク読み込み
|
||||
@param input
|
||||
@param needs_header
|
||||
@param chunk
|
||||
"""
|
||||
func _read_chunk( stream, needs_header = null ):
|
||||
var header = stream.get_string( 4 )
|
||||
if needs_header != null:
|
||||
if needs_header != header:
|
||||
print( "Doesn't exist " + needs_header + " header" )
|
||||
breakpoint
|
||||
var size = stream.get_u32( )
|
||||
var new_stream = StreamPeerBuffer.new( )
|
||||
new_stream.set_data_array( stream.get_partial_data( size )[1] )
|
||||
new_stream.big_endian = false
|
||||
|
||||
return {
|
||||
"header": header,
|
||||
"size": size,
|
||||
"stream": new_stream,
|
||||
}
|
||||
|
||||
"""
|
||||
INFOチャンクを読み込む
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_info( stream ):
|
||||
var chunk = self._read_chunk( stream, "LIST" )
|
||||
self._check_header( chunk.stream, "INFO" )
|
||||
|
||||
var info = {
|
||||
"ifil":null,
|
||||
"isng":null,
|
||||
"inam":null,
|
||||
|
||||
"irom":null,
|
||||
"iver":null,
|
||||
"icrd":null,
|
||||
"ieng":null,
|
||||
"iprd":null,
|
||||
"icop":null,
|
||||
"icmt":null,
|
||||
"isft":null,
|
||||
}
|
||||
|
||||
while 0 < chunk.stream.get_available_bytes( ):
|
||||
var sub_chunk = self._read_chunk( chunk.stream )
|
||||
match sub_chunk.header.to_lower( ):
|
||||
"ifil":
|
||||
info.ifil = self._read_version_tag( sub_chunk.stream )
|
||||
"isng":
|
||||
info.isng = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"inam":
|
||||
info.inam = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"irom":
|
||||
info.irom = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"iver":
|
||||
info.iver = self._read_version_tag( sub_chunk.stream )
|
||||
"icrd":
|
||||
info.icrd = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"ieng":
|
||||
info.ieng = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"iprd":
|
||||
info.iprd = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"icop":
|
||||
info.icop = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"icmt":
|
||||
info.icmt = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
"isft":
|
||||
info.isft = sub_chunk.stream.get_string( sub_chunk.size )
|
||||
_:
|
||||
print( "unknown header" )
|
||||
breakpoint
|
||||
|
||||
return info
|
||||
|
||||
"""
|
||||
バージョンタグを読み込む
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_version_tag( stream ):
|
||||
var major = stream.get_u16( )
|
||||
var minor = stream.get_u16( )
|
||||
|
||||
return {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
}
|
||||
|
||||
"""
|
||||
SDTAを読み込む
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_sdta( stream ):
|
||||
var chunk = self._read_chunk( stream, "LIST" )
|
||||
self._check_header( chunk.stream, "sdta" )
|
||||
|
||||
var smpl = self._read_chunk( chunk.stream, "smpl" )
|
||||
var smpl_bytes = smpl.stream.get_partial_data( smpl.size )[1]
|
||||
|
||||
var sm24_bytes = null
|
||||
if 0 < chunk.stream.get_available_bytes( ):
|
||||
var sm24_chunk = self._read_chunk( chunk.stream, "sm24" )
|
||||
sm24_bytes = sm24_chunk.stream.get_partial_data( sm24_chunk.size )[1]
|
||||
|
||||
return {
|
||||
"smpl": smpl_bytes,
|
||||
"sm24": sm24_bytes,
|
||||
}
|
||||
|
||||
"""
|
||||
PDTAを読み込む
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta( stream ):
|
||||
var chunk = self._read_chunk( stream, "LIST" )
|
||||
self._check_header( chunk.stream, "pdta" )
|
||||
|
||||
var phdr = self._read_pdta_phdr( chunk.stream )
|
||||
var pbag = self._read_pdta_bag( chunk.stream )
|
||||
var pmod = self._read_pdta_mod( chunk.stream )
|
||||
var pgen = self._read_pdta_gen( chunk.stream )
|
||||
var inst = self._read_pdta_inst( chunk.stream )
|
||||
var ibag = self._read_pdta_bag( chunk.stream )
|
||||
var imod = self._read_pdta_mod( chunk.stream )
|
||||
var igen = self._read_pdta_gen( chunk.stream )
|
||||
var shdr = self._read_pdta_shdr( chunk.stream )
|
||||
|
||||
return {
|
||||
"phdr": phdr,
|
||||
"pbag": pbag,
|
||||
"pmod": pmod,
|
||||
"pgen": pgen,
|
||||
"inst": inst,
|
||||
"ibag": ibag,
|
||||
"imod": imod,
|
||||
"igen": igen,
|
||||
"shdr": shdr,
|
||||
}
|
||||
|
||||
"""
|
||||
phdr 読み込み
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta_phdr( stream ):
|
||||
var chunk = self._read_chunk( stream, "phdr" )
|
||||
var phdrs = []
|
||||
|
||||
while 0 < chunk.stream.get_available_bytes( ):
|
||||
var phdr = {
|
||||
"name": "",
|
||||
"preset": 0,
|
||||
"bank": 0,
|
||||
"preset_bag_index": 0,
|
||||
"library": 0,
|
||||
"genre": 0,
|
||||
"morphology": 0,
|
||||
}
|
||||
|
||||
phdr.name = chunk.stream.get_string( 20 )
|
||||
phdr.preset = chunk.stream.get_u16( )
|
||||
phdr.bank = chunk.stream.get_u16( )
|
||||
phdr.preset_bag_index = chunk.stream.get_u16( )
|
||||
phdr.library = chunk.stream.get_32( )
|
||||
phdr.genre = chunk.stream.get_32( )
|
||||
phdr.morphology = chunk.stream.get_32( )
|
||||
|
||||
phdrs.append( phdr )
|
||||
|
||||
return phdrs
|
||||
|
||||
"""
|
||||
*bag読み込み
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta_bag( stream ):
|
||||
var chunk = self._read_chunk( stream )
|
||||
var bags = []
|
||||
|
||||
if chunk.header.substr( 1, 3 ) != "bag":
|
||||
print( "Doesn't exist *bag header." )
|
||||
breakpoint
|
||||
|
||||
while 0 < chunk.stream.get_available_bytes( ):
|
||||
var bag = {
|
||||
"gen_ndx": 0,
|
||||
"mod_ndx": 0,
|
||||
}
|
||||
|
||||
bag.gen_ndx = chunk.stream.get_u16( )
|
||||
bag.mod_ndx = chunk.stream.get_u16( )
|
||||
bags.append( bag )
|
||||
|
||||
return bags
|
||||
|
||||
"""
|
||||
*mod読み込み
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta_mod( stream ):
|
||||
var chunk = self._read_chunk( stream )
|
||||
var mods = []
|
||||
|
||||
if chunk.header.substr( 1, 3 ) != "mod":
|
||||
print( "Doesn't exist *mod header." )
|
||||
breakpoint
|
||||
|
||||
while 0 < chunk.stream.get_available_bytes( ):
|
||||
var mod = {
|
||||
"src_oper": null,
|
||||
"dest_oper": 0,
|
||||
"amount": 0,
|
||||
"amt_src_oper": null,
|
||||
"trans_oper": 0,
|
||||
}
|
||||
|
||||
mod.src_oper = self._read_pdta_modulator( chunk.stream.get_u16( ) )
|
||||
mod.dest_oper = chunk.stream.get_u16( )
|
||||
mod.amount = chunk.stream.get_u16( )
|
||||
mod.amt_src_oper = self._read_pdta_modulator( chunk.stream.get_u16( ) )
|
||||
mod.trans_oper = chunk.stream.get_u16( )
|
||||
mods.append( mod )
|
||||
|
||||
return mods
|
||||
|
||||
"""
|
||||
PDTA-Modulator 読み込み
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta_modulator( u ):
|
||||
return {
|
||||
"type": ( u >> 10 ) & 0x3f,
|
||||
"direction": ( u >> 8 ) & 0x01,
|
||||
"polarity": ( u >> 9 ) & 0x01,
|
||||
"controller": u & 0x7f,
|
||||
"controllerPallete": ( u >> 7 ) & 0x01,
|
||||
}
|
||||
|
||||
"""
|
||||
gen 読み込み
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta_gen( stream ):
|
||||
var chunk = self._read_chunk( stream )
|
||||
var gens = []
|
||||
|
||||
if chunk.header.substr( 1, 3 ) != "gen":
|
||||
print( "Doesn't exist *gen header." )
|
||||
breakpoint
|
||||
|
||||
while 0 < chunk.stream.get_available_bytes( ):
|
||||
var gen = {
|
||||
"gen_oper": 0,
|
||||
"amount": 0,
|
||||
"uamount": 0,
|
||||
}
|
||||
|
||||
gen.gen_oper = chunk.stream.get_u16( )
|
||||
var u = chunk.stream.get_u16( )
|
||||
gen.uamount = u
|
||||
gen.amount = u
|
||||
if 32767 < u:
|
||||
gen.amount = - ( 65536 - u )
|
||||
gens.append( gen )
|
||||
|
||||
return gens
|
||||
|
||||
"""
|
||||
inst読み込み
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta_inst( stream ):
|
||||
var chunk = self._read_chunk( stream, "inst" )
|
||||
var insts = []
|
||||
|
||||
while 0 < chunk.stream.get_available_bytes( ):
|
||||
var inst = {
|
||||
"name": "",
|
||||
"inst_bag_ndx": 0,
|
||||
}
|
||||
|
||||
inst.name = chunk.stream.get_string( 20 )
|
||||
inst.inst_bag_ndx = chunk.stream.get_u16( )
|
||||
insts.append( inst )
|
||||
|
||||
return insts
|
||||
|
||||
"""
|
||||
shdr 読み込み
|
||||
@param stream
|
||||
@param chunk
|
||||
"""
|
||||
func _read_pdta_shdr( stream ):
|
||||
var chunk = self._read_chunk( stream, "shdr" )
|
||||
var shdrs = []
|
||||
|
||||
while 0 < chunk.stream.get_available_bytes( ):
|
||||
var shdr = {
|
||||
"name": "",
|
||||
"start": 0,
|
||||
"end": 0,
|
||||
"start_loop": 0,
|
||||
"end_loop": 0,
|
||||
"sample_rate": 0,
|
||||
"original_key": 0,
|
||||
"pitch_correction": 0,
|
||||
"sample_link": 0,
|
||||
"sample_type": 0,
|
||||
}
|
||||
|
||||
shdr.name = chunk.stream.get_string( 20 )
|
||||
shdr.start = chunk.stream.get_u32( )
|
||||
shdr.end = chunk.stream.get_u32( )
|
||||
shdr.start_loop = chunk.stream.get_u32( )
|
||||
shdr.end_loop = chunk.stream.get_u32( )
|
||||
shdr.sample_rate = chunk.stream.get_u32( )
|
||||
shdr.original_key = chunk.stream.get_u8( )
|
||||
shdr.pitch_correction = chunk.stream.get_8( )
|
||||
shdr.sample_link = chunk.stream.get_u16( )
|
||||
shdr.sample_type = chunk.stream.get_u16( )
|
||||
shdrs.append( shdr )
|
||||
|
||||
return shdrs
|
||||
BIN
source/midi/Tetris - Song A.mid
Normal file
BIN
source/midi/TimGM6mb.sf2
Normal file
BIN
source/night-sky-background-14391263141jp.jpg
Normal file
|
After Width: | Height: | Size: 587 KiB |
54
source/project.godot
Normal file
@@ -0,0 +1,54 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=3
|
||||
|
||||
[application]
|
||||
|
||||
config/name="TETRIS 3000"
|
||||
run/main_scene="res://Main.tscn"
|
||||
boot_splash/image="res://splash.png"
|
||||
config/icon="res://icons/48.png"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/width=500
|
||||
window/size/height=500
|
||||
window/vsync/use_vsync=false
|
||||
window/stretch/mode="2d"
|
||||
window/stretch/aspect="expand"
|
||||
window/stretch/shrink="1"
|
||||
|
||||
[gui]
|
||||
|
||||
theme/use_hidpi=true
|
||||
|
||||
[input]
|
||||
|
||||
hold=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
pause=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
hard_drop=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
move_left=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
soft_drop=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
move_right=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
rotate_clockwise=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
rotate_counterclockwise=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
toggle_fullscreen=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
|
||||
[rendering]
|
||||
|
||||
environment/default_clear_color=Color( 0, 0, 0, 1 )
|
||||
BIN
source/splash.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |