npc.gd 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. # NPC脚本
  2. extends CharacterBody2D # ⭐ 改为CharacterBody2D
  3. # NPC信息
  4. @export var npc_name: String = "张三"
  5. @export var npc_title: String = "Python工程师"
  6. # NPC外观配置
  7. @export var sprite_frames: SpriteFrames = null # 自定义精灵帧资源
  8. # NPC移动配置 ⭐
  9. @export var move_speed: float = 50.0 # 移动速度
  10. @export var wander_enabled: bool = true # 是否启用巡逻
  11. @export var wander_range: float = 200.0 # 巡逻范围
  12. @export var wander_interval_min: float = 3.0 # 最小巡逻间隔(秒)
  13. @export var wander_interval_max: float = 8.0 # 最大巡逻间隔(秒)
  14. # 当前对话内容(从后端获取)
  15. var current_dialogue: String = ""
  16. # 节点引用
  17. @onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
  18. @onready var interaction_area: Area2D = $InteractionArea
  19. @onready var name_label: Label = $NameLabel
  20. @onready var dialogue_label: Label = $DialogueLabel
  21. # 交互提示 (可选节点,如果不存在也不会报错)
  22. var interaction_hint: Label = null
  23. # 玩家引用
  24. var player: Node = null
  25. # 巡逻相关变量 ⭐
  26. var wander_target: Vector2 = Vector2.ZERO # 巡逻目标位置
  27. var wander_timer: float = 0.0 # 巡逻计时器
  28. var is_wandering: bool = false # 是否正在巡逻
  29. var is_interacting: bool = false # 是否正在与玩家交互
  30. var spawn_position: Vector2 = Vector2.ZERO # 出生位置
  31. func _ready():
  32. # 添加到npcs组 ⭐
  33. add_to_group("npcs")
  34. # 设置NPC名字
  35. name_label.text = npc_name
  36. # 连接交互区域信号
  37. interaction_area.body_entered.connect(_on_body_entered)
  38. interaction_area.body_exited.connect(_on_body_exited)
  39. # 初始化对话标签
  40. dialogue_label.text = ""
  41. dialogue_label.visible = false
  42. # 尝试获取交互提示节点 (可选)
  43. interaction_hint = get_node_or_null("InteractionHint")
  44. if interaction_hint:
  45. interaction_hint.text = "按E交互"
  46. interaction_hint.visible = false
  47. print("[INFO] NPC交互提示已启用: ", npc_name)
  48. else:
  49. print("[WARN] NPC没有InteractionHint节点,交互提示已禁用: ", npc_name)
  50. # 设置自定义精灵帧 (如果有)
  51. if sprite_frames != null:
  52. animated_sprite.sprite_frames = sprite_frames
  53. print("[INFO] NPC使用自定义精灵: ", npc_name)
  54. # 播放默认动画
  55. if animated_sprite.sprite_frames != null and animated_sprite.sprite_frames.has_animation("idle"):
  56. animated_sprite.play("idle")
  57. # 记录出生位置 ⭐
  58. spawn_position = global_position
  59. # 初始化巡逻计时器 ⭐
  60. if wander_enabled:
  61. wander_timer = randf_range(wander_interval_min, wander_interval_max)
  62. choose_new_wander_target()
  63. Config.log_info("NPC初始化: " + npc_name)
  64. func _on_body_entered(body: Node2D):
  65. """玩家进入交互范围"""
  66. print("[DEBUG] NPC ", npc_name, " 检测到物体进入: ", body.name, " 是否在player组: ", body.is_in_group("player"))
  67. if body.is_in_group("player"):
  68. player = body
  69. print("[INFO] ✅ 玩家进入NPC范围: ", npc_name)
  70. if player.has_method("set_nearby_npc"):
  71. player.set_nearby_npc(self)
  72. else:
  73. print("[ERROR] 玩家没有set_nearby_npc方法!")
  74. # 显示提示
  75. show_interaction_hint()
  76. func _on_body_exited(body: Node2D):
  77. """玩家离开交互范围"""
  78. print("[DEBUG] NPC ", npc_name, " 检测到物体离开: ", body.name)
  79. if body.is_in_group("player"):
  80. print("[INFO] ❌ 玩家离开NPC范围: ", npc_name)
  81. if player != null and player.has_method("set_nearby_npc"):
  82. player.set_nearby_npc(null)
  83. player = null
  84. # 隐藏提示
  85. hide_interaction_hint()
  86. func show_interaction_hint():
  87. """显示交互提示"""
  88. if interaction_hint:
  89. interaction_hint.visible = true
  90. print("[INFO] 显示交互提示: ", npc_name)
  91. func hide_interaction_hint():
  92. """隐藏交互提示"""
  93. if interaction_hint:
  94. interaction_hint.visible = false
  95. print("[INFO] 隐藏交互提示: ", npc_name)
  96. func update_dialogue(dialogue: String):
  97. """更新NPC对话内容"""
  98. current_dialogue = dialogue
  99. dialogue_label.text = dialogue
  100. dialogue_label.visible = true
  101. # 10秒后隐藏对话 (增加显示时间)
  102. await get_tree().create_timer(10.0).timeout
  103. dialogue_label.visible = false
  104. func get_npc_name() -> String:
  105. return npc_name
  106. func get_npc_title() -> String:
  107. return npc_title
  108. # ⭐ 物理更新 - 处理移动
  109. func _physics_process(delta: float):
  110. """物理更新 - 处理移动"""
  111. # 如果正在与玩家交互,停止移动
  112. if is_interacting:
  113. velocity = Vector2.ZERO
  114. move_and_slide()
  115. # 播放idle动画
  116. if animated_sprite.sprite_frames != null and animated_sprite.sprite_frames.has_animation("idle"):
  117. animated_sprite.play("idle")
  118. return
  119. # 如果未启用巡逻,不移动
  120. if not wander_enabled:
  121. return
  122. # 更新巡逻计时器
  123. wander_timer -= delta
  124. # 如果计时器结束,选择新目标并开始移动
  125. if wander_timer <= 0:
  126. choose_new_wander_target()
  127. wander_timer = randf_range(wander_interval_min, wander_interval_max)
  128. # 如果正在巡逻,移动到目标
  129. if is_wandering:
  130. # 检查是否到达目标
  131. if global_position.distance_to(wander_target) < 10:
  132. # 到达目标,停止移动
  133. is_wandering = false
  134. velocity = Vector2.ZERO
  135. move_and_slide()
  136. # 播放idle动画
  137. if animated_sprite.sprite_frames != null and animated_sprite.sprite_frames.has_animation("idle"):
  138. animated_sprite.play("idle")
  139. else:
  140. # 继续移动到目标
  141. var direction = (wander_target - global_position).normalized()
  142. velocity = direction * move_speed
  143. move_and_slide()
  144. # 更新动画
  145. update_animation(direction)
  146. else:
  147. # 停止移动
  148. velocity = Vector2.ZERO
  149. move_and_slide()
  150. # 播放idle动画
  151. if animated_sprite.sprite_frames != null and animated_sprite.sprite_frames.has_animation("idle"):
  152. animated_sprite.play("idle")
  153. # ⭐ 选择新的巡逻目标
  154. func choose_new_wander_target():
  155. """选择新的巡逻目标"""
  156. # 在出生位置附近随机选择一个点
  157. var offset = Vector2(
  158. randf_range(-wander_range, wander_range),
  159. randf_range(-wander_range, wander_range)
  160. )
  161. wander_target = spawn_position + offset
  162. is_wandering = true
  163. Config.log_info("NPC %s 选择新目标: %s" % [npc_name, wander_target])
  164. # ⭐ 更新动画
  165. func update_animation(direction: Vector2):
  166. """更新动画"""
  167. if animated_sprite.sprite_frames == null:
  168. return
  169. if direction.length() > 0:
  170. # 移动动画
  171. if abs(direction.x) > abs(direction.y):
  172. # 左右移动
  173. if direction.x > 0:
  174. if animated_sprite.sprite_frames.has_animation("walk_right"):
  175. animated_sprite.play("walk_right")
  176. elif animated_sprite.sprite_frames.has_animation("walk"):
  177. animated_sprite.play("walk")
  178. animated_sprite.flip_h = false
  179. else:
  180. if animated_sprite.sprite_frames.has_animation("walk_left"):
  181. animated_sprite.play("walk_left")
  182. elif animated_sprite.sprite_frames.has_animation("walk"):
  183. animated_sprite.play("walk")
  184. animated_sprite.flip_h = true
  185. else:
  186. # 上下移动
  187. if direction.y > 0:
  188. if animated_sprite.sprite_frames.has_animation("walk_down"):
  189. animated_sprite.play("walk_down")
  190. elif animated_sprite.sprite_frames.has_animation("walk"):
  191. animated_sprite.play("walk")
  192. else:
  193. if animated_sprite.sprite_frames.has_animation("walk_up"):
  194. animated_sprite.play("walk_up")
  195. elif animated_sprite.sprite_frames.has_animation("walk"):
  196. animated_sprite.play("walk")
  197. else:
  198. # 静止动画
  199. if animated_sprite.sprite_frames.has_animation("idle"):
  200. animated_sprite.play("idle")
  201. # ⭐ 设置交互状态
  202. func set_interacting(interacting: bool):
  203. """设置交互状态"""
  204. is_interacting = interacting
  205. if interacting:
  206. Config.log_info("NPC %s 进入交互状态,停止移动" % npc_name)
  207. else:
  208. Config.log_info("NPC %s 退出交互状态,恢复移动" % npc_name)