创建新工程

This commit is contained in:
moyin
2025-12-05 19:00:14 +08:00
commit ff4fa5fffd
227 changed files with 32804 additions and 0 deletions

View File

@@ -0,0 +1,542 @@
extends CharacterBody2D
class_name CharacterController
## 角色控制器
## 处理角色的移动、动画和状态
# 角色属性
var character_id: String = ""
var character_name: String = ""
var is_online: bool = true
var move_speed: float = preload("res://scripts/GameConfig.gd").get_character_move_speed()
# 个性化属性
var character_level: int = 1
var character_experience: int = 0
var character_status: String = "active"
var character_mood: String = "neutral"
var character_appearance: Dictionary = {}
var character_personality: Dictionary = {}
var character_attributes: Dictionary = {}
var character_skills: Dictionary = {}
var character_achievements: Array = []
# 状态管理器
var status_manager: CharacterStatusManager
# 角色状态枚举
enum CharacterState {
IDLE,
WALKING,
TALKING
}
var current_state: CharacterState = CharacterState.IDLE
# 移动相关
var current_direction: Vector2 = Vector2.ZERO
var target_position: Vector2 = Vector2.ZERO
var is_moving_smooth: bool = false
var position_tween: Tween = null
# 交互相关
var interaction_range: float = preload("res://scripts/GameConfig.gd").get_interaction_range()
var nearby_character: CharacterController = null
# 信号
signal state_changed(old_state: CharacterState, new_state: CharacterState)
signal position_updated(new_position: Vector2)
signal interaction_available(target_character: CharacterController)
signal interaction_unavailable()
func _ready():
"""初始化角色控制器"""
# 设置碰撞层
collision_layer = 4 # Layer 3 (2^2 = 4): 角色层
collision_mask = 3 # Layers 1-2: 墙壁和家具
# 确保有碰撞形状
_ensure_collision_shape()
# 初始化位置和速度
target_position = global_position
velocity = Vector2.ZERO # 确保初始速度为零
# 初始化状态管理器
status_manager = CharacterStatusManager.new()
add_child(status_manager)
# 连接状态管理器信号
status_manager.status_changed.connect(_on_status_changed)
status_manager.mood_changed.connect(_on_mood_changed)
## 确保角色有碰撞形状
func _ensure_collision_shape() -> void:
"""
确保角色有碰撞形状,如果没有则创建默认的
"""
var collision_shape = get_node_or_null("CollisionShape2D")
if not collision_shape:
# 创建默认碰撞形状
collision_shape = CollisionShape2D.new()
var shape = CircleShape2D.new()
shape.radius = 16.0 # 默认半径
collision_shape.shape = shape
add_child(collision_shape)
collision_shape.name = "CollisionShape2D"
func _physics_process(delta: float):
"""物理帧更新"""
if is_moving_smooth:
_update_smooth_movement(delta)
else:
# 总是更新直接移动,即使方向为零(这样可以停止角色)
_update_direct_movement(delta)
## 直接移动(用于本地玩家输入)
func move_to(direction: Vector2) -> void:
"""
根据方向向量移动角色
@param direction: 移动方向(已归一化)
"""
current_direction = direction
if direction != Vector2.ZERO:
_change_state(CharacterState.WALKING)
else:
_change_state(CharacterState.IDLE)
velocity = Vector2.ZERO # 立即停止移动
## 平滑移动到目标位置(用于网络同步)
func set_position_smooth(target_pos: Vector2, use_tween: bool = false) -> void:
"""
平滑移动到目标位置(用于网络延迟补偿)
@param target_pos: 目标位置
@param use_tween: 是否使用 Tween 动画(更平滑但可能有延迟)
"""
target_position = target_pos
if use_tween:
# 使用 Tween 实现更平滑的移动
if position_tween:
position_tween.kill()
position_tween = create_tween()
var distance = global_position.distance_to(target_pos)
var duration = distance / move_speed
position_tween.tween_property(self, "global_position", target_pos, duration)
position_tween.finished.connect(_on_tween_finished)
_change_state(CharacterState.WALKING)
else:
# 使用物理移动(更适合实时网络同步)
is_moving_smooth = true
_change_state(CharacterState.WALKING)
## Tween 完成回调
func _on_tween_finished() -> void:
"""Tween 动画完成时调用"""
_change_state(CharacterState.IDLE)
position_updated.emit(global_position)
## 直接设置位置(瞬移)
func set_position_immediate(pos: Vector2) -> void:
"""
立即设置角色位置(无动画)
@param pos: 新位置
"""
global_position = pos
target_position = pos
is_moving_smooth = false
position_updated.emit(pos)
## 播放动画
func play_animation(anim_name: String) -> void:
"""
播放指定动画
@param anim_name: 动画名称idle, walking, talking
"""
# 查找 AnimationPlayer 节点
var anim_player = get_node_or_null("AnimationPlayer")
if anim_player and anim_player is AnimationPlayer:
# 检查动画是否存在
if anim_player.has_animation(anim_name):
anim_player.play(anim_name)
# 如果没有 AnimationPlayer使用简单的视觉反馈
_update_animation_state(anim_name)
## 设置在线状态
func set_online_status(online: bool) -> void:
"""
设置角色在线/离线状态
@param online: 是否在线
"""
is_online = online
_update_visual_status()
## 获取当前状态
func get_current_state() -> CharacterState:
"""
获取当前角色状态
@return: 当前状态
"""
return current_state
## 初始化角色数据
func initialize(data: Dictionary) -> void:
"""
使用角色数据初始化控制器
@param data: 角色数据字典
"""
print("CharacterController.initialize() called with data: ", data)
if data.has(CharacterData.FIELD_ID):
character_id = data[CharacterData.FIELD_ID]
if data.has(CharacterData.FIELD_NAME):
character_name = data[CharacterData.FIELD_NAME]
if data.has(CharacterData.FIELD_IS_ONLINE):
is_online = data[CharacterData.FIELD_IS_ONLINE]
# 加载个性化数据
_load_personalization_data(data)
# 设置初始位置
var pos = CharacterData.get_position(data)
print("Setting character position from data: ", pos)
set_position_immediate(pos)
# 完全重置所有移动状态
_reset_movement_state()
# 设置状态管理器数据
if status_manager:
status_manager.set_character_data(data)
# 更新名称标签
_update_name_label()
_update_visual_status()
_update_appearance()
## 重置移动状态
func _reset_movement_state() -> void:
"""完全重置角色的移动状态"""
velocity = Vector2.ZERO
current_direction = Vector2.ZERO
target_position = global_position
is_moving_smooth = false
# 停止任何正在进行的Tween
if position_tween:
position_tween.kill()
position_tween = null
# 设置为空闲状态
_change_state(CharacterState.IDLE)
print("Character movement state completely reset")
## 更新名称标签
func _update_name_label() -> void:
"""更新角色名称标签"""
var name_label = get_node_or_null("NameLabel")
if not name_label:
# 使用工具类创建带阴影的标签
name_label = preload("res://scripts/Utils.gd").create_label_with_shadow("", 14)
name_label.name = "NameLabel"
add_child(name_label)
# 设置名称文本(包含等级和心情)
var display_name = character_name if not character_name.is_empty() else "Unknown"
var personalization = preload("res://scripts/CharacterPersonalization.gd")
var mood_emoji = personalization.get_mood_emoji(character_mood)
var level_text = "Lv.%d" % character_level if character_level > 1 else ""
var full_text = display_name
if not level_text.is_empty():
full_text += " " + level_text
full_text += " " + mood_emoji
name_label.text = full_text
# 调整位置(在角色上方)
name_label.position = Vector2(-60, -40) # 居中并在角色上方
name_label.size = Vector2(120, 20)
## 私有方法:更新直接移动
func _update_direct_movement(_delta: float) -> void:
"""
更新基于输入的直接移动
@param _delta: 帧时间(保留用于未来扩展)
"""
# 计算速度
if current_direction == Vector2.ZERO:
velocity = Vector2.ZERO
else:
velocity = current_direction.normalized() * move_speed
# 执行移动并处理碰撞
var previous_position = global_position
move_and_slide()
# 检查是否发生了碰撞
if get_slide_collision_count() > 0:
# 发生碰撞,角色被阻挡
pass
# 发射位置更新信号(只在位置改变时)
if global_position != previous_position:
target_position = global_position
position_updated.emit(global_position)
## 私有方法:更新平滑移动
func _update_smooth_movement(_delta: float) -> void:
"""
更新平滑移动到目标位置
@param _delta: 帧时间(未使用,保留用于未来扩展)
"""
var distance = global_position.distance_to(target_position)
# 如果已经很接近目标位置,停止移动
if distance < 1.0:
global_position = target_position
is_moving_smooth = false
_change_state(CharacterState.IDLE)
position_updated.emit(global_position)
return
# 计算移动方向和速度
var direction = (target_position - global_position).normalized()
velocity = direction * move_speed
# 执行移动
move_and_slide()
position_updated.emit(global_position)
## 私有方法:改变状态
func _change_state(new_state: CharacterState) -> void:
"""
改变角色状态
@param new_state: 新状态
"""
if current_state == new_state:
return
var old_state = current_state
current_state = new_state
state_changed.emit(old_state, new_state)
# 根据状态播放动画
match new_state:
CharacterState.IDLE:
play_animation("idle")
CharacterState.WALKING:
play_animation("walking")
CharacterState.TALKING:
play_animation("talking")
## 私有方法:更新动画状态
func _update_animation_state(_anim_name: String) -> void:
"""
更新动画状态的视觉反馈(当没有 AnimationPlayer 时)
@param _anim_name: 动画名称(未使用,保留用于未来扩展)
"""
# 这是一个占位实现,用于在没有实际动画资源时提供反馈
# 实际的精灵动画将在任务 11 中添加
pass
## 检测附近角色
func check_nearby_characters(all_characters: Array) -> void:
"""
检测附近是否有可交互的角色
@param all_characters: 所有角色的数组
"""
var closest_character: CharacterController = null
var closest_distance: float = interaction_range
for character in all_characters:
if character == self or not character is CharacterController:
continue
var distance = global_position.distance_to(character.global_position)
if distance < closest_distance:
closest_distance = distance
closest_character = character
# 检查是否有变化
if closest_character != nearby_character:
nearby_character = closest_character
if nearby_character:
interaction_available.emit(nearby_character)
else:
interaction_unavailable.emit()
## 获取附近角色
func get_nearby_character() -> CharacterController:
"""
获取当前附近的角色
@return: 附近的角色,如果没有则返回 null
"""
return nearby_character
## 私有方法:更新视觉状态
func _update_visual_status() -> void:
"""
更新角色的视觉状态(在线/离线)
"""
# 查找或创建状态指示器
var status_indicator = get_node_or_null("StatusIndicator")
if not status_indicator:
# 使用工具类创建状态指示器
status_indicator = preload("res://scripts/Utils.gd").create_status_indicator(is_online)
status_indicator.name = "StatusIndicator"
status_indicator.position = Vector2(-4, -24) # 在角色上方
add_child(status_indicator)
else:
# 更新现有指示器的颜色
var personalization = preload("res://scripts/CharacterPersonalization.gd")
var color = personalization.get_status_color(character_status) if is_online else Color.GRAY
status_indicator.color = color
## 加载个性化数据
func _load_personalization_data(data: Dictionary) -> void:
"""
从角色数据中加载个性化信息
@param data: 角色数据字典
"""
character_level = data.get(CharacterData.FIELD_LEVEL, 1)
character_experience = data.get(CharacterData.FIELD_EXPERIENCE, 0)
character_status = data.get(CharacterData.FIELD_STATUS, "active")
character_mood = data.get(CharacterData.FIELD_MOOD, "neutral")
character_appearance = data.get(CharacterData.FIELD_APPEARANCE, {})
character_personality = data.get(CharacterData.FIELD_PERSONALITY, {})
character_attributes = data.get(CharacterData.FIELD_ATTRIBUTES, {})
character_skills = data.get(CharacterData.FIELD_SKILLS, {})
character_achievements = data.get(CharacterData.FIELD_ACHIEVEMENTS, [])
## 更新外观
func _update_appearance() -> void:
"""更新角色外观"""
var sprite = get_node_or_null("CharacterSprite")
if sprite and sprite is CharacterSprite:
var personalization = preload("res://scripts/CharacterPersonalization.gd")
personalization.apply_appearance_to_sprite(sprite, character_appearance)
## 状态变化回调
func _on_status_changed(old_status: String, new_status: String) -> void:
"""状态变化时的回调"""
character_status = new_status
_update_visual_status()
print("Character %s status changed: %s -> %s" % [character_name, old_status, new_status])
## 心情变化回调
func _on_mood_changed(old_mood: String, new_mood: String) -> void:
"""心情变化时的回调"""
character_mood = new_mood
_update_name_label() # 更新名称标签以显示心情
print("Character %s mood changed: %s -> %s" % [character_name, old_mood, new_mood])
## 获取角色信息摘要
func get_character_summary() -> String:
"""
获取角色信息摘要
@return: 角色摘要文本
"""
var summary = []
summary.append("等级 %d" % character_level)
if character_achievements.size() > 0:
summary.append("%d 个成就" % character_achievements.size())
var personalization = preload("res://scripts/CharacterPersonalization.gd")
var mood_emoji = personalization.get_mood_emoji(character_mood)
summary.append("心情 %s" % mood_emoji)
return " | ".join(summary)
## 设置角色状态
func set_character_status(status: String) -> void:
"""
设置角色状态
@param status: 新状态
"""
if status_manager:
status_manager.set_status(status)
## 设置角色心情
func set_character_mood(mood: String) -> void:
"""
设置角色心情
@param mood: 新心情
"""
if status_manager:
status_manager.set_mood(mood)
## 触发活动事件
func trigger_activity_event(event_type: String, data: Dictionary = {}) -> void:
"""
触发角色活动事件
@param event_type: 事件类型
@param data: 事件数据
"""
if status_manager:
status_manager.handle_activity_event(event_type, data)
## 增加经验值
func add_experience(experience: int) -> bool:
"""
增加经验值
@param experience: 经验值
@return: 是否升级
"""
character_experience += experience
# 检查升级
var required_exp = CharacterData.get_required_experience(character_level)
if character_experience >= required_exp:
character_level += 1
character_experience -= required_exp
# 触发升级事件
trigger_activity_event("level_up")
print("Character %s leveled up to %d!" % [character_name, character_level])
return true
return false
## 添加成就
func add_achievement(achievement: Dictionary) -> void:
"""
添加成就
@param achievement: 成就数据
"""
# 检查是否已有此成就
for existing in character_achievements:
if existing.get("id") == achievement.get("id"):
return
character_achievements.append(achievement)
trigger_activity_event("achievement_earned")
print("Character %s earned achievement: %s" % [character_name, achievement.get("name", "Unknown")])
## 获取个性化数据
func get_personalization_data() -> Dictionary:
"""
获取当前的个性化数据
@return: 个性化数据字典
"""
return {
CharacterData.FIELD_LEVEL: character_level,
CharacterData.FIELD_EXPERIENCE: character_experience,
CharacterData.FIELD_STATUS: character_status,
CharacterData.FIELD_MOOD: character_mood,
CharacterData.FIELD_APPEARANCE: character_appearance,
CharacterData.FIELD_PERSONALITY: character_personality,
CharacterData.FIELD_ATTRIBUTES: character_attributes,
CharacterData.FIELD_SKILLS: character_skills,
CharacterData.FIELD_ACHIEVEMENTS: character_achievements
}