优化NPC2D,增加 hook_speaking&unhook_speaking;孤儿院第一次与大胖对话使用该功能
This commit is contained in:
parent
9c3c470f3e
commit
70f9d35d07
@ -2,10 +2,15 @@
|
||||
class_name Npc2D extends AnimatedSprite2D
|
||||
|
||||
signal interacted
|
||||
# 在 unlock player 之前发射
|
||||
signal talk_finished
|
||||
signal talk_finished # 在 unlock player 之前发射
|
||||
|
||||
# <0 means no walk to edge
|
||||
# 常量定义
|
||||
const SPEAKING_SIGN_FADE_DURATION := 0.3
|
||||
const SPEAKING_SCALE_MULTIPLIER := 1.3
|
||||
|
||||
enum SpeakingSignMode { HIDDEN = 0, SILENT = 1, SPEAKING = 2 }
|
||||
|
||||
# 导出变量
|
||||
@export var snap_to_edge := true
|
||||
@export var walk_to_edge_width := 25.0
|
||||
@export var action_key := 4
|
||||
@ -14,40 +19,46 @@ signal talk_finished
|
||||
enabled = val
|
||||
if is_node_ready():
|
||||
_align_signs_status()
|
||||
|
||||
@export var sign_mark_height := 10.0:
|
||||
set(val):
|
||||
sign_mark_height = val
|
||||
if is_node_ready():
|
||||
if is_node_ready() and sign_mark:
|
||||
sign_mark.sign_mark_offset.y = -sign_mark_height
|
||||
|
||||
@export var speaking_sign_height := 60.0:
|
||||
set(val):
|
||||
speaking_sign_height = val
|
||||
if is_node_ready():
|
||||
if is_node_ready() and speaking_sign:
|
||||
speaking_sign.position.y = -speaking_sign_height
|
||||
|
||||
@export var sign_x_offset := 0.0:
|
||||
set(val):
|
||||
sign_x_offset = val
|
||||
if is_node_ready():
|
||||
speaking_sign.position.x = sign_x_offset
|
||||
sign_mark.position.x = sign_x_offset
|
||||
_update_sign_x_positions()
|
||||
|
||||
@export var collision_width_and_x := Vector2(20.0, 0):
|
||||
set(val):
|
||||
collision_width_and_x = val
|
||||
if is_node_ready():
|
||||
var shape = area2d.get_node("CollisionShape2D").shape
|
||||
shape.size.x = collision_width_and_x.x
|
||||
area2d.position.x = collision_width_and_x.y
|
||||
_update_collision_shape()
|
||||
|
||||
# 节点引用
|
||||
@onready var speaking_animation = %SpeakingAnimationPlayer
|
||||
@onready var speaking_sign = %SpeakingSign2D as Node2D
|
||||
@onready var sign_mark = %Sign as Sign
|
||||
@onready var sign_snapper = %SignSnapper as SignSnapper
|
||||
@onready var area2d = %Area2D as Area2D
|
||||
|
||||
# 内部变量
|
||||
var ground_archive: GroundArchive
|
||||
# 尝试互动的次数
|
||||
var icount: int:
|
||||
set(val):
|
||||
if icount == val:
|
||||
return
|
||||
icount = val
|
||||
if ground_archive:
|
||||
ground_archive.set_pair(name, "icount", val)
|
||||
_align_signs_status()
|
||||
|
||||
@ -56,107 +67,248 @@ var dialogue_res = preload("res://asset/dialogue/npc.dialogue")
|
||||
|
||||
var base_scale := Vector2.ONE
|
||||
var base_mod := Color.WHITE_SMOKE
|
||||
|
||||
var speaking_sign_tween: Tween
|
||||
|
||||
# 0 hide; 1 silent; 2 speaking
|
||||
var speaking_sign_mode := 0:
|
||||
var speaking_sign_mode := SpeakingSignMode.HIDDEN:
|
||||
set(val):
|
||||
if speaking_sign_mode != val:
|
||||
if speaking_sign_mode == val:
|
||||
return
|
||||
speaking_sign_mode = val
|
||||
if speaking_sign_tween and speaking_sign_tween.is_valid():
|
||||
speaking_sign_tween.kill()
|
||||
speaking_sign_tween = create_tween()
|
||||
if val == 0:
|
||||
speaking_sign_tween.tween_property(speaking_sign, "modulate:a", 0.0, 0.3)
|
||||
speaking_animation.stop()
|
||||
elif val == 1:
|
||||
speaking_sign_tween.tween_property(speaking_sign, "modulate", base_mod, 0.3)
|
||||
speaking_sign_tween.parallel().tween_property(
|
||||
speaking_sign, "scale", base_scale, 0.3
|
||||
)
|
||||
speaking_animation.play("speaking")
|
||||
elif val == 2:
|
||||
speaking_sign_tween.tween_property(speaking_sign, "modulate", Color.WHITE, 0.3)
|
||||
speaking_sign_tween.parallel().tween_property(
|
||||
speaking_sign, "scale", base_scale * 1.3, 0.3
|
||||
)
|
||||
speaking_animation.play("speaking")
|
||||
_update_speaking_sign_mode()
|
||||
|
||||
# 强制播放状态管理
|
||||
var is_hooked := false
|
||||
var hook_id := 0 # 用于追踪hook会话,避免异步问题
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# sign position
|
||||
_initialize_components()
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
_setup_editor_preview()
|
||||
return
|
||||
|
||||
_setup_game_mode()
|
||||
_align_signs_status()
|
||||
|
||||
|
||||
func _initialize_components() -> void:
|
||||
# 设置标记位置
|
||||
if sign_mark:
|
||||
sign_mark.sign_mark_offset.y = -sign_mark_height
|
||||
speaking_sign.position.y = -speaking_sign_height
|
||||
sign_mark.position.x = sign_x_offset
|
||||
|
||||
if speaking_sign:
|
||||
speaking_sign.position.y = -speaking_sign_height
|
||||
speaking_sign.position.x = sign_x_offset
|
||||
# collisiong shape
|
||||
var shape = area2d.get_node("CollisionShape2D").shape
|
||||
shape.size.x = collision_width_and_x.x
|
||||
area2d.position.x = collision_width_and_x.y
|
||||
sign_snapper.action_on_arrived = action_key
|
||||
sign_snapper.radius = walk_to_edge_width
|
||||
sign_snapper.enabled = snap_to_edge
|
||||
# 设置 speaking_sign 默认值
|
||||
base_scale = speaking_sign.scale
|
||||
base_mod = speaking_sign.modulate
|
||||
speaking_sign.modulate.a = 0.0
|
||||
if Engine.is_editor_hint():
|
||||
# editor 下都显示
|
||||
|
||||
# 设置碰撞形状
|
||||
_update_collision_shape()
|
||||
|
||||
# 配置 sign_snapper
|
||||
if sign_snapper:
|
||||
sign_snapper.action_on_arrived = action_key
|
||||
sign_snapper.radius = walk_to_edge_width
|
||||
sign_snapper.enabled = snap_to_edge
|
||||
|
||||
|
||||
func _setup_editor_preview() -> void:
|
||||
if speaking_sign:
|
||||
speaking_sign.visible = true
|
||||
speaking_sign.modulate.a = 1.0
|
||||
speaking_sign.get_node("Sprite2D").position.x = -60.0
|
||||
speaking_sign.get_node("Sprite2D").frame = 2
|
||||
var sprite = speaking_sign.get_node_or_null("Sprite2D")
|
||||
if sprite:
|
||||
sprite.position.x = -60.0
|
||||
sprite.frame = 2
|
||||
|
||||
if sign_mark:
|
||||
sign_mark.display_sign = true
|
||||
return
|
||||
# setup default value
|
||||
|
||||
|
||||
func _setup_game_mode() -> void:
|
||||
# 获取存档数据
|
||||
ground_archive = ArchiveManager.archive.ground_archive()
|
||||
icount = ground_archive.get_value(name, "icount", 0)
|
||||
if snap_to_edge:
|
||||
|
||||
# 连接信号
|
||||
if snap_to_edge and sign_snapper:
|
||||
sign_snapper.arrived.connect(_on_interacted)
|
||||
else:
|
||||
elif sign_mark:
|
||||
sign_mark.interacted.connect(_on_interacted)
|
||||
# sign_mark.cancel.connect(_stop_speaking)
|
||||
|
||||
if sign_mark:
|
||||
sign_mark.toggle_active.connect(_on_toggle_active)
|
||||
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
|
||||
# 开始动画
|
||||
if sprite_frames and animation:
|
||||
play()
|
||||
|
||||
|
||||
func _update_sign_x_positions() -> void:
|
||||
if speaking_sign:
|
||||
speaking_sign.position.x = sign_x_offset
|
||||
if sign_mark:
|
||||
sign_mark.position.x = sign_x_offset
|
||||
|
||||
|
||||
func _update_collision_shape() -> void:
|
||||
if not area2d:
|
||||
return
|
||||
|
||||
var collision_shape = area2d.get_node_or_null("CollisionShape2D")
|
||||
if collision_shape and collision_shape.shape:
|
||||
collision_shape.shape.size.x = collision_width_and_x.x
|
||||
area2d.position.x = collision_width_and_x.y
|
||||
|
||||
|
||||
func _update_speaking_sign_mode() -> void:
|
||||
if not speaking_sign:
|
||||
return
|
||||
|
||||
# 清理之前的补间动画
|
||||
if speaking_sign_tween and speaking_sign_tween.is_valid():
|
||||
speaking_sign_tween.kill()
|
||||
|
||||
speaking_sign_tween = create_tween()
|
||||
|
||||
match speaking_sign_mode:
|
||||
SpeakingSignMode.HIDDEN:
|
||||
speaking_sign_tween.tween_property(
|
||||
speaking_sign, "modulate:a", 0.0, SPEAKING_SIGN_FADE_DURATION
|
||||
)
|
||||
if speaking_animation:
|
||||
speaking_animation.stop()
|
||||
|
||||
SpeakingSignMode.SILENT:
|
||||
speaking_sign_tween.tween_property(
|
||||
speaking_sign, "modulate", base_mod, SPEAKING_SIGN_FADE_DURATION
|
||||
)
|
||||
speaking_sign_tween.parallel().tween_property(
|
||||
speaking_sign, "scale", base_scale, SPEAKING_SIGN_FADE_DURATION
|
||||
)
|
||||
if speaking_animation:
|
||||
speaking_animation.play("speaking")
|
||||
|
||||
SpeakingSignMode.SPEAKING:
|
||||
speaking_sign_tween.tween_property(
|
||||
speaking_sign, "modulate", Color.WHITE, SPEAKING_SIGN_FADE_DURATION
|
||||
)
|
||||
speaking_sign_tween.parallel().tween_property(
|
||||
speaking_sign,
|
||||
"scale",
|
||||
base_scale * SPEAKING_SCALE_MULTIPLIER,
|
||||
SPEAKING_SIGN_FADE_DURATION
|
||||
)
|
||||
if speaking_animation:
|
||||
speaking_animation.play("speaking")
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
_align_signs_status()
|
||||
|
||||
|
||||
func _align_signs_status():
|
||||
sign_mark.enabled = enabled and is_visible_in_tree()
|
||||
func _align_signs_status() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
var is_active = enabled and is_visible_in_tree()
|
||||
if sign_mark:
|
||||
sign_mark.enabled = is_active
|
||||
sign_mark.display_sign = icount == 0
|
||||
speaking_sign.visible = enabled and icount > 0
|
||||
if speaking_sign:
|
||||
speaking_sign.visible = enabled and (icount > 0 or is_hooked)
|
||||
|
||||
|
||||
func _on_toggle_active(activated: bool) -> void:
|
||||
# 如果处于hook状态,不响应正常的toggle
|
||||
if is_hooked:
|
||||
return
|
||||
if not activated:
|
||||
speaking_sign_mode = 0
|
||||
elif speaking_sign_mode == 0:
|
||||
speaking_sign_mode = 1
|
||||
speaking_sign_mode = SpeakingSignMode.HIDDEN
|
||||
elif speaking_sign_mode == SpeakingSignMode.HIDDEN:
|
||||
speaking_sign_mode = SpeakingSignMode.SILENT
|
||||
|
||||
|
||||
func _on_interacted() -> void:
|
||||
# play dialogue
|
||||
if dialogue_title:
|
||||
if not dialogue_title:
|
||||
return
|
||||
|
||||
# 如果正在hook播放,先取消hook
|
||||
if is_hooked:
|
||||
_cancel_hook()
|
||||
|
||||
SceneManager.lock_player(0, action_key)
|
||||
icount += 1
|
||||
|
||||
if ground_archive:
|
||||
ground_archive.set_pair(name, "icount", icount)
|
||||
|
||||
DialogueManager.show_dialogue_balloon(dialogue_res, dialogue_title)
|
||||
interacted.emit()
|
||||
var out_of_range = speaking_sign_mode == 0
|
||||
speaking_sign_mode = 2
|
||||
|
||||
var was_out_of_range = speaking_sign_mode == SpeakingSignMode.HIDDEN
|
||||
speaking_sign_mode = SpeakingSignMode.SPEAKING
|
||||
|
||||
await DialogueManager.dialogue_ended
|
||||
speaking_sign_mode = 0 if out_of_range else 1
|
||||
# 在 unlock 之前发射
|
||||
|
||||
speaking_sign_mode = SpeakingSignMode.HIDDEN if was_out_of_range else SpeakingSignMode.SILENT
|
||||
talk_finished.emit()
|
||||
SceneManager.unlock_player()
|
||||
|
||||
|
||||
# 新增:强制播放说话动画
|
||||
func hook_speaking() -> int:
|
||||
# 强制显示说话气泡动画
|
||||
# 返回值:hook会话ID,用于验证unhook的有效性
|
||||
is_hooked = true
|
||||
hook_id += 1
|
||||
var current_hook_id = hook_id
|
||||
|
||||
# 确保speaking_sign可见
|
||||
if speaking_sign and not speaking_sign.visible:
|
||||
speaking_sign.visible = true
|
||||
|
||||
# 保存当前状态并切换到说话模式
|
||||
speaking_sign_mode = SpeakingSignMode.SPEAKING
|
||||
|
||||
return current_hook_id
|
||||
|
||||
|
||||
# 新增:退出强制播放
|
||||
func unhook_speaking(session_id: int = -1) -> void:
|
||||
# 退出强制播放模式
|
||||
# 参数:
|
||||
# session_id: hook会话ID,如果不匹配则忽略此次unhook
|
||||
# 如果已经不在hook状态,或session_id不匹配,则忽略
|
||||
if not is_hooked or (session_id != -1 and session_id != hook_id):
|
||||
return
|
||||
_cancel_hook()
|
||||
|
||||
|
||||
# 新增:内部取消hook的方法
|
||||
func _cancel_hook() -> void:
|
||||
# 内部使用的取消hook方法
|
||||
is_hooked = false
|
||||
|
||||
# 恢复到适当的状态
|
||||
if sign_mark and sign_mark.activated:
|
||||
speaking_sign_mode = SpeakingSignMode.SILENT
|
||||
else:
|
||||
speaking_sign_mode = SpeakingSignMode.HIDDEN
|
||||
|
||||
# 重新对齐显示状态
|
||||
_align_signs_status()
|
||||
|
||||
|
||||
# 新增:检查是否正在hook播放
|
||||
func is_hook_speaking() -> bool:
|
||||
"""返回当前是否处于强制播放状态"""
|
||||
return is_hooked
|
||||
|
||||
|
||||
func _get(property: StringName) -> Variant:
|
||||
if property == "dialogue_title":
|
||||
return dialogue_title
|
||||
@ -172,8 +324,9 @@ func _set(property: StringName, value: Variant) -> bool:
|
||||
|
||||
func _get_property_list() -> Array[Dictionary]:
|
||||
var hint_str = ""
|
||||
if Engine.is_editor_hint():
|
||||
if Engine.is_editor_hint() and dialogue_res:
|
||||
hint_str = ",".join(dialogue_res.get_ordered_titles())
|
||||
|
||||
return [
|
||||
{
|
||||
"name": "dialogue_title",
|
||||
|
@ -94,8 +94,10 @@ func pre_game_intro():
|
||||
var p = $"../DeployLayer/四小孩画鬼差的对话ambush/FocusPoint"
|
||||
camera.focus_node(p, 3.0)
|
||||
await Util.wait(2.0)
|
||||
_hook_npc3_speaking()
|
||||
DialogueManager.show_dialogue_balloon(dialogue_c01, "c01_s06_四个小孩画鬼差的对话")
|
||||
await DialogueManager.dialogue_ended
|
||||
_unhook_npc3_speaking()
|
||||
# 重置镜头
|
||||
SceneManager.focus_player_and_reset_zoom(2.5)
|
||||
await Util.wait(2.5)
|
||||
@ -123,14 +125,17 @@ func game_intro() -> void:
|
||||
# DialogueManager.show_dialogue_balloon(
|
||||
# dialogue_c01, "c01_s06_谈论鬼差与猫鼠游戏", [GlobalConfig.DIALOG_IGNORE_INPUT]
|
||||
# )
|
||||
_hook_npc3_speaking()
|
||||
DialogueManager.show_dialogue_balloon(dialogue_c01, "c01_s06_谈论鬼差与猫鼠游戏")
|
||||
DialogueManager.dialogue_ended.connect(_game_counting_down, CONNECT_ONE_SHOT)
|
||||
await DialogueManager.dialogue_ended
|
||||
_game_counting_down()
|
||||
|
||||
|
||||
func _game_counting_down(_res = null):
|
||||
$"Sfx猫鼠游戏".play()
|
||||
DialogueManager.show_dialogue_balloon(dialogue_c01, "c01_s06_猫鼠游戏BGM开始")
|
||||
await DialogueManager.dialogue_ended
|
||||
_unhook_npc3_speaking()
|
||||
# 重置镜头
|
||||
SceneManager.focus_player_and_reset_zoom(2.5)
|
||||
SceneManager.release_player()
|
||||
@ -145,6 +150,14 @@ func _game_counting_down(_res = null):
|
||||
cat.get_node("猫咪嘶吼音效").play()
|
||||
|
||||
|
||||
func _hook_npc3_speaking(_res = null) -> void:
|
||||
game_kid.get_node("Npc对话3").hook_speaking()
|
||||
|
||||
|
||||
func _unhook_npc3_speaking(_res = null) -> void:
|
||||
game_kid.get_node("Npc对话3").unhook_speaking()
|
||||
|
||||
|
||||
# 玩家与三个小孩的互动计数
|
||||
func _on_talked(id: int):
|
||||
#talk count
|
||||
|
@ -71,7 +71,7 @@ func _knock_door():
|
||||
await Util.wait(2.2)
|
||||
$"敲门音效".play()
|
||||
await Util.wait(1.2)
|
||||
var stream = preload("res://asset/audio/sfx/交互/序章/03_书店外黄昏_开门.ogg")
|
||||
var stream = preload("uid://ehgd455wq8to")
|
||||
AudioManager.play_sfx(stream)
|
||||
|
||||
|
||||
@ -95,3 +95,14 @@ func seller_interacted():
|
||||
# 播放获得动画
|
||||
SceneManager.enable_prop_item("prop_信碎片2")
|
||||
SceneManager.release_player()
|
||||
|
||||
|
||||
func jiandu_dialog_triggered() -> void:
|
||||
var jiandu = $"../DeployLayer/举碗小孩/Npc监督小孩"
|
||||
jiandu.hook_speaking()
|
||||
DialogueManager.dialogue_ended.connect(_on_jiandu_dialog_ended, CONNECT_ONE_SHOT)
|
||||
|
||||
|
||||
func _on_jiandu_dialog_ended(_res) -> void:
|
||||
var jiandu = $"../DeployLayer/举碗小孩/Npc监督小孩"
|
||||
jiandu.unhook_speaking()
|
||||
|
@ -203,12 +203,6 @@ sprite_frames = ExtResource("6_thm8f")
|
||||
animation = &"杂戏团黄昏-其余小孩"
|
||||
autoplay = "杂戏团黄昏-其余小孩"
|
||||
|
||||
[node name="Ambush监督小孩" parent="Ground/DeployLayer/其余小孩" instance=ExtResource("9_f61dl")]
|
||||
position = Vector2(-688, 53)
|
||||
cooldown_time = 0.1
|
||||
lock_player_on_playing_dialogue = false
|
||||
hook_dialogue_title = "c01_s07_监督小孩吉祥话"
|
||||
|
||||
[node name="Npc吉祥话1" parent="Ground/DeployLayer/其余小孩" instance=ExtResource("6_fw22n")]
|
||||
position = Vector2(-44, 78)
|
||||
sign_mark_height = 23.0
|
||||
@ -237,6 +231,20 @@ autoplay = "杂戏团黄昏_举碗小孩"
|
||||
position = Vector2(6, 57)
|
||||
note_key = "c01_s07_钱碗"
|
||||
|
||||
[node name="Npc监督小孩" parent="Ground/DeployLayer/举碗小孩" instance=ExtResource("6_fw22n")]
|
||||
position = Vector2(6, 72)
|
||||
snap_to_edge = false
|
||||
enabled = false
|
||||
sign_mark_height = 11.0
|
||||
speaking_sign_height = 54.0
|
||||
|
||||
[node name="Ambush监督小孩" parent="Ground/DeployLayer/举碗小孩" instance=ExtResource("9_f61dl")]
|
||||
position = Vector2(-825, 53)
|
||||
cooldown_time = 0.1
|
||||
lock_player_on_playing_dialogue = false
|
||||
hook_dialogue_title = "c01_s07_监督小孩吉祥话"
|
||||
hook_method = "jiandu_dialog_triggered"
|
||||
|
||||
[node name="报童" parent="Ground/DeployLayer" index="10" instance=ExtResource("9_slaub")]
|
||||
position = Vector2(2080, 6)
|
||||
sprite_frames = ExtResource("6_thm8f")
|
||||
|
Loading…
Reference in New Issue
Block a user