543 lines
16 KiB
GDScript
543 lines
16 KiB
GDScript
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
|
||
}
|