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

11 KiB
Raw Permalink Blame History

实现细节规范

本文档详细说明了WhaleTown项目中各种游戏对象的具体实现要求和技术细节。

🎮 玩家实现规范

基础要求

  • 节点类型: 必须使用 CharacterBody2D
  • 移动系统: 使用 move_and_slide() 方法
  • 相机集成: 必须包含 Camera2D 子节点

玩家节点结构

Player (CharacterBody2D)
├── Sprite2D                    # 玩家精灵
├── CollisionShape2D            # 碰撞形状
├── Camera2D                    # 玩家相机
│   └── [相机配置]
├── InteractionArea (Area2D)    # 交互检测区域
│   └── CollisionShape2D
└── AnimationPlayer             # 动画播放器

相机配置要求

# 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

移动实现模板

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实现规范

基础要求

  • 节点类型: 使用 CharacterBody2DStaticBody2D
  • 交互区域: 必须包含名为 InteractionAreaArea2D
  • 事件通信: 通过 EventSystem 触发交互

NPC节点结构

NPC (CharacterBody2D)
├── Sprite2D                    # NPC精灵
├── CollisionShape2D            # 物理碰撞
├── InteractionArea (Area2D)    # 交互检测区域
│   └── CollisionShape2D        # 交互碰撞形状
├── DialogueComponent           # 对话组件
└── AnimationPlayer             # 动画播放器

NPC实现模板

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排序: 禁用
# 设置地面层
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排序: 禁用
# 设置障碍层
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排序: 启用(重要!)
# 设置装饰层
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设置模板

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)

🎯 交互物实现规范

可收集物品

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()

可交互对象

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"
    })

🎨 资源过滤设置

纹理过滤规范

所有像素艺术资源必须使用最近邻过滤:

# 在导入设置中或代码中设置
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 (开启)

🔧 性能优化要求

节点缓存

# ✅ 正确:使用@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  # 每帧都要查找节点

事件频率控制

# 控制事件发送频率
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

记住:遵循这些实现细节规范可以确保游戏对象的一致性和性能!