Files
whale-town-front/docs/03-技术实现/实现细节规范.md
moyin 1ff677b3b2 docs: 重新组织文档结构,按开发阶段分类
新的目录结构:
  01-项目入门/     # 新人必读,项目基础
  02-开发规范/     # 编码标准和规范
  03-技术实现/     # 具体开发指导
  04-高级开发/     # 进阶开发技巧
  05-部署运维/     # 发布和部署
  06-功能模块/     # 特定功能文档

 新增导航文档:
- docs/README.md - 完整的文档导航和使用指南
- 各目录下的README.md - 分类说明和使用指导

 优化效果:
- 开发者可以按阶段快速定位需要的文档
- 新人有清晰的学习路径
- 不同角色有针对性的文档推荐
- 提供了问题导向的快速查找功能
2025-12-31 18:02:16 +08:00

388 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 实现细节规范
本文档详细说明了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
```
---
**记住:遵循这些实现细节规范可以确保游戏对象的一致性和性能!**