# 实现细节规范 本文档详细说明了WhaleTown项目中各种游戏对象的具体实现要求和技术细节。 ## 🎮 玩家实现规范 ### 基础要求 - **节点类型**: 必须使用 `CharacterBody2D` - **移动系统**: 使用 `move_and_slide()` 方法 - **相机集成**: 必须包含 `Camera2D` 子节点 ### 玩家节点结构 ``` Player (CharacterBody2D) ├── Sprite2D # 玩家精灵 ├── CollisionShape2D # 碰撞形状 ├── Camera2D # 玩家相机 │ └── [相机配置] ├── InteractionArea (Area2D) # 交互检测区域 │ └── CollisionShape2D └── AnimationPlayer # 动画播放器 ``` ### 相机配置要求 ```gdscript # Player.gd 中的相机设置 @onready var camera: Camera2D = $Camera2D func _ready() -> void: # 必须启用位置平滑 camera.position_smoothing_enabled = true camera.position_smoothing_speed = 5.0 # 设置相机边界(基于TileMap) _setup_camera_limits() func _setup_camera_limits() -> void: # 获取当前场景的TileMap var tilemap = get_tree().get_first_node_in_group("tilemap") if tilemap and tilemap is TileMap: var used_rect = tilemap.get_used_rect() var tile_size = tilemap.tile_set.tile_size # 计算世界坐标边界 camera.limit_left = used_rect.position.x * tile_size.x camera.limit_top = used_rect.position.y * tile_size.y camera.limit_right = used_rect.end.x * tile_size.x camera.limit_bottom = used_rect.end.y * tile_size.y ``` ### 移动实现模板 ```gdscript extends CharacterBody2D class_name Player @export var move_speed: float = 200.0 @export var acceleration: float = 1000.0 @export var friction: float = 1000.0 @onready var sprite: Sprite2D = $Sprite2D @onready var camera: Camera2D = $Camera2D func _ready() -> void: # 设置相机 camera.position_smoothing_enabled = true _setup_camera_limits() func _physics_process(delta: float) -> void: _handle_movement(delta) func _handle_movement(delta: float) -> void: # 获取输入方向 var input_direction := Input.get_vector( "move_left", "move_right", "move_up", "move_down" ) # 应用移动 if input_direction != Vector2.ZERO: velocity = velocity.move_toward(input_direction * move_speed, acceleration * delta) else: velocity = velocity.move_toward(Vector2.ZERO, friction * delta) move_and_slide() # 发送移动事件 if velocity.length() > 0: EventSystem.emit_event(EventNames.PLAYER_MOVED, { "position": global_position, "velocity": velocity }) ``` ## 🤖 NPC实现规范 ### 基础要求 - **节点类型**: 使用 `CharacterBody2D` 或 `StaticBody2D` - **交互区域**: 必须包含名为 `InteractionArea` 的 `Area2D` - **事件通信**: 通过 `EventSystem` 触发交互 ### NPC节点结构 ``` NPC (CharacterBody2D) ├── Sprite2D # NPC精灵 ├── CollisionShape2D # 物理碰撞 ├── InteractionArea (Area2D) # 交互检测区域 │ └── CollisionShape2D # 交互碰撞形状 ├── DialogueComponent # 对话组件 └── AnimationPlayer # 动画播放器 ``` ### NPC实现模板 ```gdscript extends CharacterBody2D class_name NPC @export var npc_name: String = "NPC" @export var dialogue_resource: DialogueResource @onready var interaction_area: Area2D = $InteractionArea @onready var sprite: Sprite2D = $Sprite2D signal interaction_available(npc: NPC) signal interaction_unavailable(npc: NPC) func _ready() -> void: # 连接交互区域信号 interaction_area.body_entered.connect(_on_interaction_area_entered) interaction_area.body_exited.connect(_on_interaction_area_exited) # 监听交互事件 EventSystem.connect_event(EventNames.INTERACT_PRESSED, _on_interact_pressed) func _on_interaction_area_entered(body: Node2D) -> void: if body.is_in_group("player"): interaction_available.emit(self) # 显示交互提示 _show_interaction_hint() func _on_interaction_area_exited(body: Node2D) -> void: if body.is_in_group("player"): interaction_unavailable.emit(self) # 隐藏交互提示 _hide_interaction_hint() func _on_interact_pressed(data: Dictionary = {}) -> void: # 检查玩家是否在交互范围内 if _is_player_in_range(): start_dialogue() func start_dialogue() -> void: EventSystem.emit_event(EventNames.NPC_TALKED, { "npc": self, "npc_name": npc_name, "dialogue": dialogue_resource }) func _is_player_in_range() -> bool: var bodies = interaction_area.get_overlapping_bodies() for body in bodies: if body.is_in_group("player"): return true return false ``` ## 🗺️ TileMap图层规范 ### 图层配置要求 TileMap必须按以下标准配置图层: #### 图层0:地面层 (Ground) - **用途**: 地面纹理、道路、草地等 - **碰撞**: 禁用物理层 - **渲染**: 最底层渲染 - **Y排序**: 禁用 ```gdscript # 设置地面层 var ground_layer = tilemap.get_layer(0) tilemap.set_layer_name(0, "Ground") tilemap.set_layer_enabled(0, true) tilemap.set_layer_y_sort_enabled(0, false) # 不设置物理层 ``` #### 图层1:障碍层 (Obstacles) - **用途**: 墙壁、树木、建筑等不可通过的障碍 - **碰撞**: 启用物理层 - **渲染**: 中间层 - **Y排序**: 禁用 ```gdscript # 设置障碍层 tilemap.set_layer_name(1, "Obstacles") tilemap.set_layer_enabled(1, true) tilemap.set_layer_y_sort_enabled(1, false) # 设置物理层用于碰撞检测 tilemap.set_layer_physics_enabled(1, true) ``` #### 图层2:装饰层 (Decoration) - **用途**: 装饰物、前景元素 - **碰撞**: 根据需要设置 - **渲染**: 最上层 - **Y排序**: 启用(重要!) ```gdscript # 设置装饰层 tilemap.set_layer_name(2, "Decoration") tilemap.set_layer_enabled(2, true) tilemap.set_layer_y_sort_enabled(2, true) # 启用Y排序 tilemap.set_layer_y_sort_origin(2, 16) # 设置排序原点 ``` ### TileMap设置模板 ```gdscript extends TileMap class_name GameTileMap func _ready() -> void: # 设置TileMap为tilemap组 add_to_group("tilemap") # 配置图层 _setup_layers() # 通知相机系统更新边界 EventSystem.emit_event(EventNames.TILEMAP_READY, { "tilemap": self, "used_rect": get_used_rect() }) func _setup_layers() -> void: # 确保有足够的图层 while get_layers_count() < 3: add_layer(-1) # 配置地面层 (0) set_layer_name(0, "Ground") set_layer_y_sort_enabled(0, false) # 配置障碍层 (1) set_layer_name(1, "Obstacles") set_layer_y_sort_enabled(1, false) set_layer_physics_enabled(1, true) # 配置装饰层 (2) set_layer_name(2, "Decoration") set_layer_y_sort_enabled(2, true) set_layer_y_sort_origin(2, tile_set.tile_size.y / 2) ``` ## 🎯 交互物实现规范 ### 可收集物品 ```gdscript extends Area2D class_name CollectibleItem @export var item_name: String = "Item" @export var item_value: int = 1 @onready var sprite: Sprite2D = $Sprite2D @onready var collision: CollisionShape2D = $CollisionShape2D func _ready() -> void: body_entered.connect(_on_body_entered) func _on_body_entered(body: Node2D) -> void: if body.is_in_group("player"): collect_item(body) func collect_item(collector: Node2D) -> void: # 发送收集事件 EventSystem.emit_event(EventNames.ITEM_COLLECTED, { "item_name": item_name, "item_value": item_value, "collector": collector, "position": global_position }) # 播放收集动画 _play_collect_animation() func _play_collect_animation() -> void: var tween = create_tween() tween.parallel().tween_property(self, "scale", Vector2.ZERO, 0.3) tween.parallel().tween_property(self, "modulate:a", 0.0, 0.3) await tween.finished queue_free() ``` ### 可交互对象 ```gdscript extends StaticBody2D class_name InteractableObject @export var interaction_text: String = "按E交互" @export var can_interact: bool = true @onready var interaction_area: Area2D = $InteractionArea @onready var sprite: Sprite2D = $Sprite2D var player_in_range: bool = false func _ready() -> void: interaction_area.body_entered.connect(_on_interaction_area_entered) interaction_area.body_exited.connect(_on_interaction_area_exited) EventSystem.connect_event(EventNames.INTERACT_PRESSED, _on_interact_pressed) func _on_interaction_area_entered(body: Node2D) -> void: if body.is_in_group("player") and can_interact: player_in_range = true _show_interaction_prompt() func _on_interaction_area_exited(body: Node2D) -> void: if body.is_in_group("player"): player_in_range = false _hide_interaction_prompt() func _on_interact_pressed(data: Dictionary = {}) -> void: if player_in_range and can_interact: interact() func interact() -> void: # 子类重写此方法实现具体交互逻辑 print("与 ", name, " 交互") EventSystem.emit_event(EventNames.OBJECT_INTERACTED, { "object": self, "interaction_type": "default" }) ``` ## 🎨 资源过滤设置 ### 纹理过滤规范 所有像素艺术资源必须使用最近邻过滤: ```gdscript # 在导入设置中或代码中设置 func _setup_pixel_perfect_texture(texture: Texture2D) -> void: if texture is ImageTexture: var image = texture.get_image() image.generate_mipmaps(false) # 在导入设置中设置Filter为Off ``` ### 导入设置模板 对于所有精灵资源,在导入设置中: - **Filter**: Off (关闭) - **Mipmaps**: Off (关闭) - **Fix Alpha Border**: On (开启) ## 🔧 性能优化要求 ### 节点缓存 ```gdscript # ✅ 正确:使用@onready缓存节点引用 @onready var sprite: Sprite2D = $Sprite2D @onready var collision: CollisionShape2D = $CollisionShape2D func _process(delta: float) -> void: sprite.modulate.a = 0.5 # 使用缓存的引用 # ❌ 错误:在_process中重复获取节点 func _process(delta: float) -> void: $Sprite2D.modulate.a = 0.5 # 每帧都要查找节点 ``` ### 事件频率控制 ```gdscript # 控制事件发送频率 var last_event_time: float = 0.0 const EVENT_INTERVAL: float = 0.1 # 100ms间隔 func _process(delta: float) -> void: var current_time = Time.get_time_dict_from_system() if current_time - last_event_time >= EVENT_INTERVAL: EventSystem.emit_event(EventNames.POSITION_UPDATE, { "position": global_position }) last_event_time = current_time ``` --- **记住:遵循这些实现细节规范可以确保游戏对象的一致性和性能!**