创建新工程
This commit is contained in:
542
scripts/CharacterController.gd
Normal file
542
scripts/CharacterController.gd
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user