diff --git a/_game/Game.tscn b/_game/Game.tscn index a5fe956..db7d4fe 100644 --- a/_game/Game.tscn +++ b/_game/Game.tscn @@ -1,9 +1,8 @@ -[gd_scene load_steps=5 format=4 uid="uid://3vc8ojbiyy5w"] +[gd_scene load_steps=4 format=4 uid="uid://3vc8ojbiyy5w"] [ext_resource type="PackedScene" uid="uid://gwhff4qaouxy" path="res://_player/Avatar.tscn" id="1_fdx6o"] [ext_resource type="PackedScene" uid="uid://1l06de041i40" path="res://_levels/l_level_1.tscn" id="1_pvnxo"] [ext_resource type="PackedScene" uid="uid://cvqehvdjpoar4" path="res://_player/player_controller.tscn" id="2_j2xwq"] -[ext_resource type="PackedScene" uid="uid://knrcnoedxvm6" path="res://_props/trigger_fall_rock/trigger_fall_rock.tscn" id="3_lvu1v"] [node name="Game" type="Node2D"] @@ -14,8 +13,5 @@ tile_map_data = PackedByteArray("AAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAIAAAAAAAA auto_controlled_avatar = NodePath("../Avatar") [node name="Avatar" parent="." instance=ExtResource("1_fdx6o")] -position = Vector2(217, -276) +position = Vector2(312, 195) collision_mask = 4 - -[node name="TriggerFallRock" parent="." instance=ExtResource("3_lvu1v")] -position = Vector2(317, 213) diff --git a/_player/Avatar.tscn b/_player/Avatar.tscn index 693a0f4..86bc0ea 100644 --- a/_player/Avatar.tscn +++ b/_player/Avatar.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=38 format=3 uid="uid://gwhff4qaouxy"] +[gd_scene load_steps=39 format=3 uid="uid://gwhff4qaouxy"] [ext_resource type="Script" uid="uid://dq1g1qp66chwy" path="res://_player/avatar.gd" id="1_rkqpu"] [ext_resource type="Script" uid="uid://isu8onknb75o" path="res://_player/states/character_state_machine.gd" id="1_wvs5h"] @@ -21,6 +21,7 @@ [ext_resource type="Script" uid="uid://d0mw2e4u5u8g" path="res://_player/states/grap_hook.gd" id="16_f07it"] [ext_resource type="Script" uid="uid://diq7vk63exae7" path="res://_player/states/hook_shooting.gd" id="16_xcbik"] [ext_resource type="Script" uid="uid://ca88urm45gx2c" path="res://_player/states/dead.gd" id="17_5r2pj"] +[ext_resource type="Script" uid="uid://bqrel2r5hcmxk" path="res://_player/states/grapping.gd" id="19_u7cua"] [ext_resource type="PackedScene" uid="uid://blm8q46h3v2im" path="res://addons/reedcomponent/locomotion/wall_detector.tscn" id="20_air0b"] [ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="20_ogl63"] [ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="21_5r2pj"] @@ -126,6 +127,7 @@ grap_hook_shooting_time = 0.2 [node name="Grapping" type="LimboState" parent="PlayerHSM/Normal/GrapHook"] unique_name_in_owner = true +script = ExtResource("19_u7cua") [node name="Dash" type="LimboState" parent="PlayerHSM/Normal"] unique_name_in_owner = true @@ -142,13 +144,15 @@ dash_speed = 400.0 end_dash_speed = 190.0 climb_hop_velocity_x = 180.0 climb_hop_velocity_y = 334.0 +jump_force = 270.0 +jump_horizontal_Boost = 235.0 wall_jump_base_force_x = 260.0 wall_jump_base_force_y = 220.0 fall_maxium_speed = 430.0 -air_control_mult = 0.7 +air_control_mult = 0.85 run_accel = 2100.0 run_reduce = 420.0 -move_speed_max = 157.0 +move_speed_max = 192.0 [node name="WallDetector" parent="LocomotionComponent" instance=ExtResource("20_air0b")] unique_name_in_owner = true diff --git a/_player/avatar.gd b/_player/avatar.gd index ed3c099..de639e8 100644 --- a/_player/avatar.gd +++ b/_player/avatar.gd @@ -115,24 +115,35 @@ func get_move_input() -> Vector2: ''' func press_jump() -> void: + m_jump_press = true hsm.dispatch(&"trigger_jump") func release_jump() -> void: + m_jump_press = false hsm.dispatch(&"completed_jump") func press_dash() -> void: + m_dash_press = true hsm.dispatch(&"trigger_dash") +func release_dash() -> void: + m_dash_press = false + hsm.dispatch(&"completed_dash") + func press_climb() -> void: + m_climb_press = true hsm.dispatch(&"trigger_climb") func release_climb() -> void: + m_climb_press = false hsm.dispatch(&"completed_climb") func press_grap_hook() -> void: + m_grap_hook_press = true hsm.dispatch(&"trigger_grap_hook") func release_grap_hook() -> void: + m_grap_hook_press = true hsm.dispatch(&"completed_grap_hook") #endregion diff --git a/_player/player_locomotion.gd b/_player/player_locomotion.gd index e9bd3f3..07c73ae 100644 --- a/_player/player_locomotion.gd +++ b/_player/player_locomotion.gd @@ -36,6 +36,17 @@ class_name CelesteLocomotionComponent extends JumpLocomotionComponent @export var climb_jump_time: float = .14 #endregion +#region Custom Move (拉扯移動) +@export_category("Custom Move") +@export var custom_move_force: float = 25.0 +@export var custom_move_max_speed: float = 420.0 +@export var custom_move_stop_distance: float = 4.0 + +var _custom_move_target_node: Node = null +var _is_custom_moving: bool = false +signal hook_released +#endregion + #region 冲刺field var last_dash_count: int ##此变量为总开关,如果关闭,则在物理更新里无论如何也是不会更新Dash的,这里默认为false,因为Dash需要手动触发,最好不要常开 @@ -54,6 +65,7 @@ var _climb_hop_flag: bool = false var _is_climb_jumping: bool = false #endregion + func _init_component() -> void: super._init_component() @@ -64,8 +76,12 @@ func _physics_process(delta: float) -> void: if Engine.is_editor_hint(): return + if _is_custom_moving: + _update_custom_move(delta) + characterbody.move_and_slide() + return + if can_climb: - print("攀爬更新中") _update_climb(delta) characterbody.move_and_slide() #移动更新 return @@ -93,6 +109,78 @@ func _update_climb(delta) -> void: func _update_dash(delta) -> void: return +##特殊移動 +func _custom_move_to(target_node: Node) -> bool: + if target_node == null: + return false + # 要求目標至少能提供 global_position(2D 常見) + if not ("global_position" in target_node): + push_warning("custom_move target_node has no global_position.") + return false + + _custom_move_target_node = target_node + _is_custom_moving = true + + # 進入自定義移動時,關掉其他行為(按你需求可調) + can_dash = false + can_climb = false + return true + +## 停止特殊移動(Custom Move) +func stop_custom_move( + force_stop_velocity: bool = true, + clear_target: bool = true +) -> void: + if not _is_custom_moving: + return + + _is_custom_moving = false + + if clear_target: + _custom_move_target_node = null + + if force_stop_velocity: + characterbody.velocity = Vector2.ZERO + +##預製的保留速度的hook stop +func _release_hook_keep_momentum() -> void: + stop_custom_move(false) + hook_released.emit() + +##特殊移動更新函數 +func _update_custom_move(delta: float) -> void: + if not _is_custom_moving: + return + + # 目標被刪掉/釋放了 + if _custom_move_target_node == null or not is_instance_valid(_custom_move_target_node): + _is_custom_moving = false + _custom_move_target_node = null + return + + var target_pos: Vector2 = _custom_move_target_node.global_position + var pos: Vector2 = characterbody.global_position + var to_target := target_pos - pos + var distance := to_target.length() + + if distance <= custom_move_stop_distance: + _is_custom_moving = false + _custom_move_target_node = null + characterbody.velocity = Vector2.ZERO + return + + var dir := to_target / distance + + # 拉扯加速度:距離越遠越快(“被拉過去”的感覺) + var acceleration := dir * distance * custom_move_force + + characterbody.velocity += acceleration * delta + + # 限制最大速度 + var v_len := characterbody.velocity.length() + if v_len > custom_move_max_speed: + characterbody.velocity = characterbody.velocity / v_len * custom_move_max_speed + ##整合之后的函数,如果不需要额外的逻辑,可以直接通过这个触发Dash func integrate_dash(dash_dir: Vector2) -> bool: if not predash(dash_dir): diff --git a/_player/states/grapping.gd b/_player/states/grapping.gd new file mode 100644 index 0000000..079f90f --- /dev/null +++ b/_player/states/grapping.gd @@ -0,0 +1,60 @@ +extends LimboState + +@export_category("Hook Pull Release") +@export var release_distance: float = 12.0 +@export var release_dot_threshold: float = 0.0 +@export var min_pull_time: float = 0.08 + +var _pull_time := 0.0 +var _anchor + +func _enter() -> void: + _pull_time = 0.0 + _anchor = agent.spawn_hook_comp.get_current_hook_anchor() + + if _anchor and is_instance_valid(_anchor): + agent.locomotion_comp._custom_move_to(_anchor) + +func _update(delta: float) -> void: + _pull_time += delta + + if _pull_time < min_pull_time: + return + + if not _anchor or not is_instance_valid(_anchor): + _force_release() + return + + var character : CharacterBody2D = agent + var pos: Vector2 = character.global_position + var target_pos: Vector2 = _anchor.global_position + + var to_target := target_pos - pos + var distance := to_target.length() + + if distance <= release_distance: + _force_release() + return + + if distance > 0.001: + var dir := to_target / distance + if character.velocity.dot(dir) <= release_dot_threshold: + _force_release() + return + +func _exit() -> void: + agent.locomotion_comp.stop_custom_move(false) + _anchor = null + +func _force_release() -> void: + agent.locomotion_comp.stop_custom_move(false) + + if agent.spawn_hook_comp.has_method("release_hook"): + agent.spawn_hook_comp.release_hook() + + ##解除hook + if agent.is_on_floor(): + self.dispatch(&"exit_on_ground") + else: + self.dispatch(&"exit_on_air") + return diff --git a/_player/states/grapping.gd.uid b/_player/states/grapping.gd.uid new file mode 100644 index 0000000..791b0aa --- /dev/null +++ b/_player/states/grapping.gd.uid @@ -0,0 +1 @@ +uid://bqrel2r5hcmxk diff --git a/_player/states/hook_shooting.gd b/_player/states/hook_shooting.gd index 6ef9259..540eb01 100644 --- a/_player/states/hook_shooting.gd +++ b/_player/states/hook_shooting.gd @@ -23,7 +23,6 @@ func _exit() -> void: pass func _handle_hook_input_completed() -> bool: - return true agent.spawn_hook_comp.suspend_hook_stretching(false) return true @@ -36,3 +35,5 @@ func _handle_hook_stretching_end(reach_end: bool,Anchor:Node2D) -> void: else: self.dispatch(&"exit_on_air") return + + self.dispatch(self.EVENT_FINISHED) diff --git a/addons/reedcomponent/grap_hook/garpping_hook_v2.tscn b/addons/reedcomponent/grap_hook/garpping_hook_v2.tscn index 9a232d8..cef2596 100644 --- a/addons/reedcomponent/grap_hook/garpping_hook_v2.tscn +++ b/addons/reedcomponent/grap_hook/garpping_hook_v2.tscn @@ -1,9 +1,7 @@ -[gd_scene load_steps=3 format=3 uid="uid://ddwoxlqluxiq5"] +[gd_scene load_steps=2 format=3 uid="uid://ddwoxlqluxiq5"] [ext_resource type="Script" uid="uid://bvxgviq7l64ck" path="res://addons/reedcomponent/grap_hook/garpping_hook_v_2.gd" id="1_jrg4x"] -[sub_resource type="CircleShape2D" id="CircleShape2D_jrg4x"] - [node name="GarppingHookV2" type="Node2D"] script = ExtResource("1_jrg4x") @@ -12,12 +10,8 @@ unique_name_in_owner = true points = PackedVector2Array(0, 0, 80, 0) width = 8.0 -[node name="Area2D" type="Area2D" parent="."] +[node name="RayCast2D" type="RayCast2D" parent="."] unique_name_in_owner = true -position = Vector2(80, 0) -collision_layer = 0 +target_position = Vector2(80, 0) collision_mask = 4 - -[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] -shape = SubResource("CircleShape2D_jrg4x") -debug_color = Color(0, 0.6399631, 0.3615963, 0.41960785) +collide_with_areas = true diff --git a/addons/reedcomponent/grap_hook/garpping_hook_v_2.gd b/addons/reedcomponent/grap_hook/garpping_hook_v_2.gd index ea968a3..0cf1b7d 100644 --- a/addons/reedcomponent/grap_hook/garpping_hook_v_2.gd +++ b/addons/reedcomponent/grap_hook/garpping_hook_v_2.gd @@ -1,144 +1,159 @@ -class_name Hook extends Node2D +class_name Hook +extends Node2D ## ================ ## Export Field ## ================ -@export var min_length := 140 -@export var max_length := 200 -@export var streching_speed:float = 1400 +@export var min_length := 140.0 +@export var max_length := 200.0 +@export var stretching_speed: float = 1400.0 -## 线段 @onready var line_2d: Line2D = %Line2D -## 检测碰撞的区域 -@onready var area_2d: Area2D = %Area2D - +@onready var ray: RayCast2D = %RayCast2D const GRAPABLE_GROUP = &"GRAPABLE" -const ANCHOR_NODE = preload("uid://dfm5wy1rmci68") -signal stretching_finished(reach_limit:bool, anchor_node: Node2D) +signal stretching_finished(reach_limit: bool, anchor_node: Node2D) ## ================ ## Private Field ## ================ var _binded_hook_comp -var _is_stretching: bool = false -var _stretching_dir: Vector2 = Vector2.ZERO -var _cached_cancel: bool = false +var _is_stretching := false +var _stretching_dir := Vector2.ZERO +var _cached_cancel := false +var _current_length := 0.0 var _anchor: Node2D -func _ready() -> void: - area_2d.position = Vector2.ZERO - - area_2d.area_entered.connect(grap_detected) - area_2d.body_entered.connect(grap_detected) +# ================= +# Life Cycle +# ================= -##初始化 -func init(hook_comp:SpawnHookComponet,reset_to_target: bool): - +func _ready() -> void: + ray.enabled = true + ray.target_position = Vector2.ZERO + +## 初始化 +func init(hook_comp: SpawnHookComponet, reset_to_target: bool) -> void: _binded_hook_comp = hook_comp if reset_to_target: - var p : Vector2= hook_comp.owner.global_position - self.global_position = p + global_position = hook_comp.owner.global_position + +# ================= +# Stretch Control +# ================= -##开始stretching func start_stretching(direction: Vector2) -> void: _is_stretching = true _cached_cancel = false - _stretching_dir = direction + _stretching_dir = direction.normalized() + _current_length = 0.0 -##结束stretching func end_stretching(force_end: bool = false) -> bool: - - ##如果还没有达到最短的stretching length,则继续 if not force_end and _is_stretching: - var d = self.global_position.distance_to(area_2d.global_position) - if d < min_length: + if _current_length < min_length: _cached_cancel = true return false - + _is_stretching = false _stretching_dir = Vector2.ZERO return true -##是否正在stretching func is_stretching() -> bool: return _is_stretching -##物理更新 +# ================= +# Update +# ================= + func _physics_process(delta: float) -> void: if _is_stretching: _update_stretching(delta) -##更新绳索的动画表现 -func _process(delta: float) -> void: - update_line_target_pos_with_index(0,self.global_position) - update_line_target_pos_with_index(1,area_2d.global_position) +func _process(_delta: float) -> void: + _update_line() + +# ================= +# Core Logic +# ================= -##更新钩爪爪手的位置 func _update_stretching(delta: float) -> void: - var d = self.global_position.distance_to(area_2d.global_position) - - #如果已经存在一个缓存的取消,且当前的长度小于最小长度,则直接取消 - if _cached_cancel: - if d >= min_length: - stretching_finished.emit(true,null) - end_stretching(true) + # 先嘗試推進 + var next_length := _current_length + stretching_speed * delta + next_length = min(next_length, max_length) + + # 先用「下一幀長度」做 Ray + ray.target_position = _stretching_dir * next_length + ray.force_raycast_update() + + # ===== 命中檢測(最高優先)===== + if ray.is_colliding(): + var collider := ray.get_collider() + if collider is Node2D and collider.is_in_group(GRAPABLE_GROUP): + var hit_pos := ray.get_collision_point() + + _current_length = global_position.distance_to(hit_pos) + ray.target_position = _stretching_dir * _current_length + _handle_hit(collider as Node2D, hit_pos) return - - #如果达到的最大的长度,直接取消 - if d > max_length: - stretching_finished.emit(true,null) + + # ===== 沒命中,才正式推進 ===== + _current_length = next_length + + # 取消邏輯 + if _cached_cancel and _current_length >= min_length: + stretching_finished.emit(true, null) end_stretching(true) return - - area_2d.global_position += delta * streching_speed * _stretching_dir -## ================ -## Tool Func -## ================ - -##更新特定点的位置 -func update_line_target_pos_with_index(point_index: int, target_pos: Vector2) -> void: - line_2d.set_point_position( - point_index, - target_pos - global_position - ) - -##当触碰到可以被抓握的Area后,自动在当前位置生成一个钩爪锚点,后续的移动交给其他系统 -func grap_detected(node: Node2D) -> void: - if node.is_in_group(GRAPABLE_GROUP): + # 超過最大距離 + if _current_length >= max_length: + stretching_finished.emit(true, null) end_stretching(true) - var d : float= self.global_position.distance_to(area_2d.global_position) - var b = d == max_length - - var anchor := _create_anchor_on_node(node) - if anchor: - stretching_finished.emit(b,anchor) -##创建钩爪锚点 -func _create_anchor_on_node(area: Node2D) -> Node2D: - # 如果之前有锚点,先清掉 +func _handle_hit(target: Node2D, hit_pos: Vector2) -> void: + _is_stretching = false + _stretching_dir = Vector2.ZERO + + ray.target_position = to_local(hit_pos) + + var reach_max := is_equal_approx(_current_length, max_length) + var anchor := _create_anchor_on_node(target, hit_pos) + + stretching_finished.emit(reach_max, anchor) + +## 釋放鉤爪(清理 Anchor 與狀態) +func release_hook() -> void: + # 1. 停止拉伸(保險) + _is_stretching = false + _stretching_dir = Vector2.ZERO + _cached_cancel = false + _current_length = 0.0 + + # 2. 清掉 Anchor + if _anchor and is_instance_valid(_anchor): + # 先脫離父節點,避免殘留引用問題 + _anchor.get_parent().remove_child(_anchor) + _anchor.queue_free() + _anchor = null + + # 3. 重置 Ray 與 Line(視覺清乾淨) + ray.target_position = Vector2.ZERO + _update_line() + +func _update_line() -> void: + line_2d.set_point_position(0, Vector2.ZERO) + line_2d.set_point_position(1, ray.target_position) + +func _create_anchor_on_node(target: Node2D, hit_global_pos: Vector2) -> Node2D: if _anchor and is_instance_valid(_anchor): _anchor.queue_free() - # 1. 创建 Anchor _anchor = Node2D.new() _anchor.name = "Anchor" - add_child(_anchor) + target.add_child(_anchor) - # 2. Anchor 初始位置 = Area 当前世界位置 - _anchor.global_position = area.global_position - - # 3. 在 Area 上挂 RemoteTransform2D - var remote := RemoteTransform2D.new() - remote.name = "AnchorRemote" - area.add_child(remote) - - remote.remote_path = _anchor.get_path() - - # 4. 只同步位置(锚点一般不需要跟旋转/缩放) - remote.update_rotation = false - remote.update_scale = false + # 關鍵:固定命中點偏移 + _anchor.position = target.to_local(hit_global_pos) return _anchor diff --git a/addons/reedcomponent/grap_hook/spawn_hook_component.gd b/addons/reedcomponent/grap_hook/spawn_hook_component.gd index 29b43d8..851e5ea 100644 --- a/addons/reedcomponent/grap_hook/spawn_hook_component.gd +++ b/addons/reedcomponent/grap_hook/spawn_hook_component.gd @@ -7,11 +7,6 @@ class_name SpawnHookComponet extends ComponentBase ##用於Grapping Hook的基本Instance const GRAPPING_HOOK = preload("uid://ddwoxlqluxiq5") - -var _ray_direction: Vector2 -var _ray_reference: RayCast2D -var _ray_length: float = 100 - ##當前的Graphook的實例 var _current_grap_hook_inst: Hook @@ -30,6 +25,9 @@ func spawn_grap_hook_inst(dir: Vector2) -> Hook: i.stretching_finished.connect(_on_hook_stretching_end) return _current_grap_hook_inst +func release_cached_hook() -> void: + _current_grap_hook_inst.release_hook() + ##尝试关闭hook的延长 func suspend_hook_stretching(force_suspend: bool = false) -> bool: if not _current_grap_hook_inst: return false @@ -43,52 +41,6 @@ func _on_hook_stretching_end(reach_limit,anchor:Node2D) -> void: func get_current_hook_anchor() -> Node2D: return _current_grap_hook_inst._anchor -#func grap_reach_target(duration: float) -> bool: - #if not _ray_reference or not _current_grap_hook_inst:return false - # - ###重置所有Points的坐标点位置。 - #_current_grap_hook_inst.reset_line_points_to_target(component_owner.global_position) - # - #var start_pos: Vector2 = component_owner.global_position - #var target_pos: Vector2 = _ray_reference.get_collision_point() - # - #var t := get_tree().create_tween() - #t.set_trans(Tween.TRANS_SINE) - #t.set_ease(Tween.EASE_OUT) - # - #t.tween_method( - #_current_grap_hook_inst.update_line_anchor_pos, - #start_pos, - #target_pos, - #duration - #) - # - ###结束时会发送广播 - #t.finished.connect(_on_hook_reach_finished) - # - #return true -# -###更新GrapHook的物理 -#func update_grap_physics(input: Vector2,delta: float) -> void: - #if input.is_zero_approx(): return - #if not _current_grap_hook_inst: return - # - #var i = input.normalized() - #_current_grap_hook_inst.move_hook(input,delta) -# -###此函數默認為Vector.Zeor,子類可以重寫以得到一個全新的結果,也可以通過綁定的方式獲得一個結果。 -#func get_grap_move_input() -> Vector2: - # - ##如果我們在組件裏綁定了Component上的一個字段,也可以直接獲取。 - #if binded_hook_move_input_property_name in component_owner: - #return component_owner.get(binded_hook_move_input_property_name) - # - #return Vector2.ZERO -# -#func _on_hook_reach_finished() -> void: - #_current_grap_hook_inst.m_update_rope_with_nodes = true - #hook_reach_finished.emit() -# - +##初始化組件 func _init_component() -> void: pass