diff --git a/assets/characters/crayfish_npc_256_256.png b/assets/characters/crayfish_npc_256_256.png new file mode 100644 index 0000000..6af8dd6 Binary files /dev/null and b/assets/characters/crayfish_npc_256_256.png differ diff --git a/scenes/Maps/square.tscn b/scenes/Maps/square.tscn index c09c540..3a43919 100644 --- a/scenes/Maps/square.tscn +++ b/scenes/Maps/square.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=40 format=4 uid="uid://5cc0c6cpnhe8"] +[gd_scene load_steps=41 format=4 uid="uid://5cc0c6cpnhe8"] [ext_resource type="Script" uid="uid://b43tvo8cykfrq" path="res://scenes/Maps/BaseLevel.gd" id="1_m4als"] [ext_resource type="Texture2D" uid="uid://baa5wkuyqouh6" path="res://assets/sprites/environment/standard_brick_128_128.jpg" id="1_rb5kq"] @@ -19,6 +19,7 @@ [ext_resource type="PackedScene" uid="uid://bvfyllcy5fi8o" path="res://scenes/Maps/datawhale_home.tscn" id="16_m4als"] [ext_resource type="PackedScene" uid="uid://rdmrm7j4iokr" path="res://scenes/prefabs/items/notice_board.tscn" id="16_rixdf"] [ext_resource type="Script" uid="uid://d2od22agputjt" path="res://scenes/prefabs/items/WelcomeBoard.gd" id="16_u1t8b"] +[ext_resource type="PackedScene" path="res://scenes/characters/crayfish_npc.tscn" id="17_crayfish"] [ext_resource type="Script" uid="uid://rlkavptfhr4y" path="res://scenes/Maps/DoorTeleport.gd" id="18_0xqio"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_7nixu"] @@ -1082,6 +1083,9 @@ position = Vector2(0, 320) [node name="NPC" parent="." instance=ExtResource("15_0xqio")] position = Vector2(-81, -64) +[node name="CrayfishNpc" parent="." instance=ExtResource("17_crayfish")] +position = Vector2(88, 286) + [node name="DataWhaleHome" parent="." instance=ExtResource("16_m4als")] position = Vector2(8, -128) diff --git a/scenes/characters/NPCController.gd b/scenes/characters/NPCController.gd index 212afca..d19a1b9 100644 --- a/scenes/characters/NPCController.gd +++ b/scenes/characters/NPCController.gd @@ -1,29 +1,55 @@ extends CharacterBody2D -signal interaction_happened(text) +# ============================================================================ +# 文件名: NPCController.gd +# 作用: 通用 NPC 控制器,负责角色待机表现与交互对话 +# +# 主要功能: +# - 播放 NPC 待机动画 +# - 响应玩家射线交互 +# - 触发聊天气泡与 NPC 对话事件 +# +# 依赖: EventSystem, EventNames, ChatBubble +# 作者: Codex +# 创建时间: 2026-03-10 +# ============================================================================ + +signal interaction_happened(text: String) + +const CHAT_BUBBLE_SCENE: PackedScene = preload("res://scenes/ui/ChatBubble.tscn") @export var npc_name: String = "NPC" -@export var dialogue: String = "欢迎来到WhaleTown,我是镇长范鲸晶" +@export_multiline var dialogue: String = "欢迎来到WhaleTown,我是镇长范鲸晶" -func _ready(): - $Sprite2D.texture = preload("res://assets/characters/npc_286_241.png") - $Sprite2D.hframes = 4 - $Sprite2D.vframes = 4 - - # Start Idle Animation - if has_node("AnimationPlayer"): - $AnimationPlayer.play("idle") - - # Ensure interaction layer - collision_layer = 3 # Layer 1 & 2 (Blocking) +@onready var animation_player: AnimationPlayer = $AnimationPlayer + +func _ready() -> void: + # 播放场景里配置好的待机动画,让不同 NPC 可以复用同一个控制器。 + if animation_player.has_animation("idle"): + animation_player.play("idle") + + # 保持 NPC 可被玩家射线与角色碰撞识别。 + collision_layer = 3 collision_mask = 3 -func interact(): +# 处理玩家交互,展示气泡并向全局事件系统广播。 +func interact() -> void: show_bubble(dialogue) + EventSystem.emit_event(EventNames.NPC_TALKED, { + "npc": self, + "npc_name": npc_name, + "dialogue": dialogue + }) interaction_happened.emit(dialogue) - return null -func show_bubble(text): - var bubble = preload("res://scenes/ui/ChatBubble.tscn").instantiate() +# 在 NPC 头顶生成一次性聊天气泡。 +# +# 参数: +# text: String - 要展示的对话内容 +func show_bubble(text: String) -> void: + var bubble: Control = CHAT_BUBBLE_SCENE.instantiate() as Control + if bubble == null: + return add_child(bubble) - bubble.set_text(text) + if bubble.has_method("set_text"): + bubble.call("set_text", text) diff --git a/scenes/characters/crayfish_npc.tscn b/scenes/characters/crayfish_npc.tscn new file mode 100644 index 0000000..3f20bb1 --- /dev/null +++ b/scenes/characters/crayfish_npc.tscn @@ -0,0 +1,65 @@ +[gd_scene load_steps=7 format=3] + +[ext_resource type="Texture2D" path="res://assets/characters/crayfish_npc_256_256.png" id="1_texture"] +[ext_resource type="Script" path="res://scenes/characters/NPCController.gd" id="2_script"] + +[sub_resource type="RectangleShape2D" id="1_shape"] +size = Vector2(44, 22) + +[sub_resource type="Animation" id="2_reset"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [0] +} + +[sub_resource type="Animation" id="3_idle"] +resource_name = "idle" +length = 1.2 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0.0333333, 0.26666665, 0.4666667, 0.8, 1), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1), +"update": 1, +"values": [2, 1, 0, 4, 5] +} + +[sub_resource type="AnimationLibrary" id="4_library"] +_data = { +&"RESET": SubResource("2_reset"), +&"idle": SubResource("3_idle") +} + +[node name="CrayfishNpc" type="CharacterBody2D"] +script = ExtResource("2_script") +npc_name = "虾小满" +dialogue = "欢迎来到 WhaleTown!我是虾小满,负责看着喷泉边的水路和码头消息。想找热闹的地方,顺着水边走就对啦。" + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_texture") +hframes = 4 +vframes = 4 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +light_mask = 5 +visibility_layer = 5 +shape = SubResource("1_shape") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +&"": SubResource("4_library") +}