diff --git a/assets/characters/npc_286_241.png b/assets/characters/npc_286_241.png new file mode 100644 index 0000000..bc862ac Binary files /dev/null and b/assets/characters/npc_286_241.png differ diff --git a/assets/characters/npc_286_241.png.import b/assets/characters/npc_286_241.png.import new file mode 100644 index 0000000..8d93de0 --- /dev/null +++ b/assets/characters/npc_286_241.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brko2ik6t6ib5" +path="res://.godot/imported/npc_286_241.png-dfe6daef11d0f27f7902e69d6057828f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/characters/npc_286_241.png" +dest_files=["res://.godot/imported/npc_286_241.png-dfe6daef11d0f27f7902e69d6057828f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/Maps/square.tscn b/scenes/Maps/square.tscn index c524792..79e0995 100644 --- a/scenes/Maps/square.tscn +++ b/scenes/Maps/square.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=36 format=4 uid="uid://5cc0c6cpnhe8"] +[gd_scene load_steps=37 format=4 uid="uid://5cc0c6cpnhe8"] [ext_resource type="Script" uid="uid://c5ml4722ptwp2" 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"] @@ -11,6 +11,7 @@ [ext_resource type="Texture2D" uid="uid://5r75q24ww18f" path="res://assets/sprites/environment/river2_512_512.png" id="6_ff6l1"] [ext_resource type="Texture2D" uid="uid://d3w3fncsm32oi" path="res://assets/sprites/environment/deck_384_167.png" id="13_tct6u"] [ext_resource type="Texture2D" uid="uid://j0twhfkpj15i" path="res://assets/sprites/environment/deck_512_164.png" id="14_m4als"] +[ext_resource type="PackedScene" uid="uid://npc2282a2new" path="res://scenes/characters/npc.tscn" id="15_0xqio"] [ext_resource type="Texture2D" uid="uid://blre1srim52hs" path="res://assets/sprites/environment/deck_512_282.png" id="15_2rqka"] [ext_resource type="PackedScene" uid="uid://dscbaqkb1klwl" path="res://scenes/Maps/community.tscn" id="16_0xqio"] [ext_resource type="PackedScene" uid="uid://vq5qgk3k6t7e" path="res://scenes/Maps/fountain.tscn" id="16_2rqka"] @@ -1088,6 +1089,9 @@ position = Vector2(0, 320) [node name="DataWhaleHome" parent="." instance=ExtResource("16_m4als")] position = Vector2(8, -128) +[node name="NPC" parent="." instance=ExtResource("15_0xqio")] +position = Vector2(-88, -88) + [node name="DefaultSpawn" type="Marker2D" parent="."] position = Vector2(647, 500) diff --git a/scenes/characters/NPCController.gd b/scenes/characters/NPCController.gd new file mode 100644 index 0000000..49b3f12 --- /dev/null +++ b/scenes/characters/NPCController.gd @@ -0,0 +1,29 @@ +extends CharacterBody2D + +signal interaction_happened(text) + +@export var npc_name: String = "NPC" +@export 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) + collision_mask = 3 + +func interact(): + show_bubble(dialogue) + interaction_happened.emit(dialogue) + return null + +func show_bubble(text): + var bubble = preload("res://scenes/ui/ChatBubble.tscn").instantiate() + add_child(bubble) + bubble.set_text(text) diff --git a/scenes/characters/NPCController.gd.uid b/scenes/characters/NPCController.gd.uid new file mode 100644 index 0000000..24d4926 --- /dev/null +++ b/scenes/characters/NPCController.gd.uid @@ -0,0 +1 @@ +uid://dy3uf1rlu4h1u diff --git a/scenes/characters/PlayerController.gd b/scenes/characters/PlayerController.gd index 0b4985d..97a8c45 100644 --- a/scenes/characters/PlayerController.gd +++ b/scenes/characters/PlayerController.gd @@ -9,6 +9,7 @@ const MOVE_SPEED = 200.0 # 节点引用 @onready var animation_player: AnimationPlayer = $AnimationPlayer @onready var sprite: Sprite2D = $Sprite2D +@onready var ray_cast: RayCast2D = $RayCast2D var last_direction := "down" @@ -19,6 +20,11 @@ func _ready() -> void: # 播放初始动画 if animation_player.has_animation("idle"): animation_player.play("idle") + + # Initialize RayCast + ray_cast.add_exception(self) # Ignore local player + ray_cast.enabled = true + ray_cast.target_position = Vector2(0, 60) func _check_spawn_position() -> void: var spawn_pos = SceneManager.get_next_scene_position() @@ -27,6 +33,14 @@ func _check_spawn_position() -> void: func _physics_process(delta: float) -> void: _handle_movement(delta) + _handle_interaction() + +func _handle_interaction() -> void: + if Input.is_action_just_pressed("interact"): + if ray_cast.is_colliding(): + var collider = ray_cast.get_collider() + if collider and collider.has_method("interact"): + collider.interact() func _handle_movement(_delta: float) -> void: # 获取移动向量 (参考 docs/02-开发规范/输入映射配置.md) @@ -59,13 +73,17 @@ func _update_animation_state(direction: Vector2) -> void: if abs(direction.x) > abs(direction.y): if direction.x > 0: last_direction = "right" + ray_cast.target_position = Vector2(60, 0) else: last_direction = "left" + ray_cast.target_position = Vector2(-60, 0) else: if direction.y > 0: last_direction = "down" + ray_cast.target_position = Vector2(0, 60) else: last_direction = "up" + ray_cast.target_position = Vector2(0, -60) animation_player.play("walk_" + last_direction) diff --git a/scenes/characters/npc.tscn b/scenes/characters/npc.tscn new file mode 100644 index 0000000..1282856 --- /dev/null +++ b/scenes/characters/npc.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=7 format=3 uid="uid://npc2282a2new"] + +[ext_resource type="Texture2D" uid="uid://brko2ik6t6ib5" path="res://assets/characters/npc_286_241.png" id="1_2r34a"] +[ext_resource type="Script" uid="uid://dy3uf1rlu4h1u" path="res://scenes/characters/NPCController.gd" id="1_script"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_npc"] +size = Vector2(48, 24) + +[sub_resource type="Animation" id="Animation_2r34a"] +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="Animation_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="AnimationLibrary_npc"] +_data = { +&"RESET": SubResource("Animation_2r34a"), +&"idle": SubResource("Animation_idle") +} + +[node name="NPC" type="CharacterBody2D"] +position = Vector2(-8, 0) +script = ExtResource("1_script") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_2r34a") +hframes = 4 +vframes = 4 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +light_mask = 5 +visibility_layer = 5 +shape = SubResource("RectangleShape2D_npc") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +&"": SubResource("AnimationLibrary_npc") +} diff --git a/scenes/characters/player.tscn b/scenes/characters/player.tscn index 112c8d9..10fa637 100644 --- a/scenes/characters/player.tscn +++ b/scenes/characters/player.tscn @@ -175,3 +175,5 @@ libraries = { [node name="Camera2D" type="Camera2D" parent="."] zoom = Vector2(2, 2) + +[node name="RayCast2D" type="RayCast2D" parent="."] diff --git a/scenes/ui/ChatBubble.gd b/scenes/ui/ChatBubble.gd new file mode 100644 index 0000000..7cf5367 --- /dev/null +++ b/scenes/ui/ChatBubble.gd @@ -0,0 +1,9 @@ +extends Control + +@onready var label = $PanelContainer/Label + +func set_text(text): + label.text = text + # Destroy after 5 seconds + await get_tree().create_timer(5.0).timeout + queue_free() diff --git a/scenes/ui/ChatBubble.gd.uid b/scenes/ui/ChatBubble.gd.uid new file mode 100644 index 0000000..687203f --- /dev/null +++ b/scenes/ui/ChatBubble.gd.uid @@ -0,0 +1 @@ +uid://b4aorojcbwkmb diff --git a/scenes/ui/ChatBubble.tscn b/scenes/ui/ChatBubble.tscn new file mode 100644 index 0000000..13b13fc --- /dev/null +++ b/scenes/ui/ChatBubble.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" uid="uid://b4aorojcbwkmb" path="res://scenes/ui/ChatBubble.gd" id="1_script"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bubble_modern"] +bg_color = Color(1, 1, 1, 0.9) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 +shadow_color = Color(0, 0, 0, 0.2) +shadow_size = 2 + +[node name="ChatBubble" type="Control"] +layout_mode = 3 +anchors_preset = 0 +script = ExtResource("1_script") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -75.0 +offset_top = -60.0 +offset_right = 75.0 +offset_bottom = -20.0 +grow_horizontal = 2 +grow_vertical = 0 +theme_override_styles/panel = SubResource("StyleBoxFlat_bubble_modern") + +[node name="Label" type="Label" parent="PanelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0.1, 0.1, 0.1, 1) +theme_override_font_sizes/font_size = 8 +text = "..." +horizontal_alignment = 1 +vertical_alignment = 1 +autowrap_mode = 3 +custom_minimum_size = Vector2(150, 0)