@tool extends CanvasLayer signal exiting signal success const NON_SELECTED = [-1, -1] @export var shuffle_times := 20 @export var debug_relocate := false: set(value): debug_relocate = false if Engine.is_editor_hint(): _init_answer_and_connect_signals() _measure_width_by_row() for row in range(3): _relocate_books(row) @onready var sfx_select = $SfxSelect as Sfx @onready var sfx_interchange = $SfxInterchange as Sfx var current_answer := [] var book_width_by_row := [] var suffling := false # [row, col] var selected_book := NON_SELECTED: set(value): if selected_book == value: return # 在 suffling 时不允许改变 selected_book if suffling: selected_book = value return if selected_book != NON_SELECTED: _toggle_book(false, selected_book[0], selected_book[1]) if value != NON_SELECTED: _toggle_book(true, value[0], value[1]) selected_book = value var gameover = false func _ready() -> void: layer = GlobalConfig.CANVAS_LAYER_LITTLE_GAME # init answer first _init_answer_and_connect_signals() if Engine.is_editor_hint(): return _measure_width_by_row() # shuffle at the end _shuffle_books() func _enter_tree() -> void: SceneManager.pop_center_notification(tr("ui_press_q_to_exit")) func _init_answer_and_connect_signals() -> void: current_answer.clear() for row in range(3): var r_size = get_node("./Shelf/Layer" + str(row)).get_child_count() # current_answer append a r_size arr var arr = [] for id in range(r_size): arr.append(id) var book = _get_book_by_id(row, id) if not Engine.is_editor_hint(): book.get_node("BookButton").pressed.connect(_on_book_pressed.bind(row, id)) current_answer.append(arr) func _measure_width_by_row() -> void: # 书本间的额外间隙,用于调整书本之间的间隔 var extra_gap = [2., 3., 2.] book_width_by_row = [] for row in range(3): var length = current_answer[row].size() var width_arr = [] for col in range(length): var book_btn = _get_book_by_id(row, col).get_node("BookButton") as TextureButton width_arr.append(book_btn.texture_normal.get_width() + extra_gap[row]) book_width_by_row.append(width_arr) func _shuffle_books() -> void: selected_book = NON_SELECTED if GlobalConfig.DEBUG: SceneManager.pop_debug_dialog_info( "备注", "debug模式每列随机洗牌 1 次,正常模式每列随机洗牌 " + str(shuffle_times) + " 次" ) shuffle_times = 1 suffling = true rand_from_seed(Time.get_ticks_usec()) for row in range(3): # shuffle each row 20 times var r_size = current_answer[row].size() for _i in range(shuffle_times): var col_1 = randi() % r_size var col_2 = randi() % r_size selected_book = [row, col_1] _interchange_book(row, col_2, false) _relocate_books(row) # turn off initilazing after shuffle suffling = false func _on_book_pressed(row: int, id: int) -> void: if gameover: return var col = current_answer[row].find(id) if selected_book == NON_SELECTED: selected_book = [row, col] else: if selected_book == [row, col]: selected_book = NON_SELECTED elif selected_book[0] == row: _interchange_book(row, col) else: selected_book = [row, col] func _get_book_by_id(row: int, id: int) -> Node2D: return get_node("./Shelf/Layer" + str(row) + "/Book" + str(id)) # func _get_book_by_pos(row: int, col: int) -> Node2D: # var layer = get_node("./Shelf/Layer" + str(row)) # return layer.get_child(col) var mute_toggle = false func _toggle_book(selected: bool, row: int, col: int) -> void: var id = current_answer[row][col] var book = _get_book_by_id(row, id) var book_tween = create_tween() if not mute_toggle: sfx_select.play() if selected: book_tween.parallel().tween_property(book, "position:y", -10.0, 0.2) else: book_tween.parallel().tween_property(book, "position:y", 0.0, 0.2) func _interchange_book(row: int, col: int, relocate := true) -> void: if selected_book == NON_SELECTED or selected_book[0] != row: return var col2 = selected_book[1] sfx_interchange.play() # interchange current_answer var temp = current_answer[row][col] current_answer[row][col] = current_answer[row][col2] current_answer[row][col2] = temp # reset selected_book mute_toggle = true selected_book = NON_SELECTED mute_toggle = false # relocate after reset selected_book if relocate: _relocate_books(row) # check answer if relocate: _check_answer() func _relocate_books(row: int) -> void: selected_book = NON_SELECTED var r_size = current_answer[row].size() var x = 0 for col in range(r_size): var id = current_answer[row][col] var book = _get_book_by_id(row, id) book.position.x = x book.position.y = 0.0 x += book_width_by_row[row][id] func _check_answer() -> void: # 第一行需要顺序排列 var row1 = current_answer[0] for col in range(row1.size()): if row1[col] != col: return # 第二行需要正序排列 var row2 = current_answer[1] var size2 = row2.size() if row2[0] == 0: # 正序 for col in range(1, size2): if row2[col] != col: return else: return # # 倒序 # for col in range(size2): # if row2[col] != size2 - 1 - col: # return # 第三行正序或者倒序都可以 var _row3 = current_answer[2] var _size3 = _row3.size() if _row3[0] == 0: # 正序 for col in range(1, _size3): if _row3[col] != col: return else: # 倒序 for col in range(_size3): if _row3[col] != _size3 - 1 - col: return # # 最后一行按色块排列;0-6 蓝色(7个)在一起,7-11 红色(5个)在一起,12-17 黄色(6个)在一起 # var row3 = current_answer[2] # # 0: blue, 1: red, 2: yellow # var visited = [7, 5, 6] # var visiting_init = true # var visiting = -1 # for col in range(row3.size()): # var color = _get_color(row3[col]) # if visiting_init: # visiting = color # visiting_init = false # if color != visiting: # return # visited[color] -= 1 # if visited[color] == 0: # visiting_init = true # success _success() # 0: blue, 1: red, 2: yellow func _get_color(index: int) -> int: # 0-6 蓝色(7个)在一起,7-11 红色(5个)在一起,12-17 黄色(6个)在一起 if index < 7: return 0 elif index < 12: return 1 else: return 2 func _success() -> void: gameover = true # 生死簿倒下的动画 $"Shelf/Layer0/Book17/BookButton".hide() $"Shelf/生死簿".show() $"Shelf/生死簿".play("书架倒下") $"Shelf/生死簿/Button".pressed.connect(_on_target_book_pressed, CONNECT_ONE_SHOT) $"Sfx书本掉落".play() func _on_target_book_pressed(): $"Shelf/生死簿".play("书架扶正") await get_tree().create_timer(1.5).timeout $"生死簿".show() # 完成后释放信号 $"生死簿".finished.connect(_on_success_finished) $"Sfx扶正书本".play() # 生死簿阅读完成后 func _on_success_finished() -> void: # exit # exiting.emit() success.emit() get_parent().remove_child(self) func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("cancel"): if not gameover: get_viewport().set_input_as_handled() exiting.emit() get_parent().remove_child(self) elif event.is_action_pressed("interact"): # 阻塞交互输入 get_viewport().set_input_as_handled()