| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- import { _decorator, Component, Node, Label, find, JsonAsset, Collider2D, RigidBody2D, ERigidBody2DType, BoxCollider2D, Tween, tween, Vec3, Color } from 'cc';
- import { SaveDataManager } from '../LevelSystem/SaveDataManager';
- import EventBus, { GameEvents } from '../Core/EventBus';
- import { JsonConfigLoader } from '../Core/JsonConfigLoader';
- import { AnalyticsManager } from '../Utils/AnalyticsManager';
- import { SkillManager } from './SkillSelection/SkillManager';
- const { ccclass, property } = _decorator;
- /**
- * 墙体组件
- * 负责管理墙体的血量、伤害处理、等级升级等功能
- */
- @ccclass('Wall')
- export class Wall extends Component {
-
- @property({
- type: Node,
- tooltip: '血量显示节点 (HeartLabel)'
- })
- public heartLabelNode: Node = null;
- // === 私有属性 ===
- private currentHealth: number = 100;
- private heartLabel: Label = null;
- private saveDataManager: SaveDataManager = null;
-
- // 墙体配置数据
- private wallConfig: any = null;
- private wallHpMap: Record<number, number> = {};
-
- // 动画相关属性
- private originalLabelColor: Color = null;
- private damageAnimationTween: Tween<Node> = null;
- private colorAnimationTween: Tween<Label> = null;
- async start() {
- await this.initializeWall();
- }
- /**
- * 加载墙体配置
- */
- private async loadWallConfig(): Promise<void> {
- try {
- this.wallConfig = await JsonConfigLoader.getInstance().loadConfig('wall');
- if (this.wallConfig && this.wallConfig.wallConfig && this.wallConfig.wallConfig.healthByLevel) {
- // 转换字符串键为数字键
- const healthByLevel = this.wallConfig.wallConfig.healthByLevel;
- this.wallHpMap = {};
- for (const level in healthByLevel) {
- this.wallHpMap[parseInt(level)] = healthByLevel[level];
- }
- console.log('[Wall] 墙体配置加载成功:', this.wallHpMap);
- } else {
- console.warn('[Wall] 配置文件格式错误,使用默认配置');
- this.useDefaultConfig();
- }
- } catch (error) {
- console.error('[Wall] 加载wall.json时出错:', error);
- this.useDefaultConfig();
- }
- }
- /**
- * 使用默认配置
- */
- private useDefaultConfig(): void {
- this.wallHpMap = {
- 1: 100,
- 2: 500,
- 3: 1200,
- 4: 1500,
- 5: 2000
- };
- }
- /**
- * 设置墙体碰撞器
- * 确保墙体能够与敌人发生碰撞
- */
- private setupWallCollider(): void {
- // 检查是否已有碰撞器组件
- let collider = this.node.getComponent(Collider2D);
- if (!collider) {
- // 添加BoxCollider2D组件
- collider = this.node.addComponent(BoxCollider2D);
- console.log(`[Wall] 为墙体节点 ${this.node.name} 添加碰撞器组件`);
- }
- // 确保有RigidBody2D组件
- let rigidBody = this.node.getComponent(RigidBody2D);
- if (!rigidBody) {
- rigidBody = this.node.addComponent(RigidBody2D);
- console.log(`[Wall] 为墙体节点 ${this.node.name} 添加刚体组件`);
- }
- // 设置刚体属性
- if (rigidBody) {
- rigidBody.type = ERigidBody2DType.Static; // 静态刚体
- rigidBody.enabledContactListener = true; // 启用碰撞监听
- }
- // 设置碰撞器属性
- if (collider) {
- collider.sensor = false; // 不是传感器,会产生物理碰撞
- }
- console.log(`[Wall] 墙体 ${this.node.name} 碰撞器设置完成,碰撞器启用: ${collider?.enabled}, 刚体启用: ${rigidBody?.enabled}`);
- }
- /**
- * 初始化墙体
- */
- private async initializeWall() {
- // 初始化存档管理器
- this.saveDataManager = SaveDataManager.getInstance();
- if (!this.saveDataManager) {
- console.error('[Wall] SaveDataManager not found');
- return;
- }
- // 加载墙体配置
- await this.loadWallConfig();
-
- // 查找血量显示节点
- this.findHeartLabelNode();
-
- // 从存档读取墙体血量
- this.loadWallHealthFromSave();
-
- // 初始化血量显示
- this.updateHealthDisplay();
-
- // 设置墙体碰撞器
- this.setupWallCollider();
-
- // 监听治疗技能变化
- this.setupSkillListeners();
-
- // 设置事件监听器
- this.setupEventListeners();
- }
- /**
- * 查找血量显示节点
- */
- private findHeartLabelNode() {
- // 查找心血显示节点
- if (!this.heartLabelNode) {
- this.heartLabelNode = find('Canvas-001/TopArea/HeartNode/HeartLabel') || find('Canvas/GameLevelUI/HeartNode/HeartLabel');
- }
-
- if (this.heartLabelNode) {
- this.heartLabel = this.heartLabelNode.getComponent(Label);
- // 保存原始颜色
- if (this.heartLabel && !this.originalLabelColor) {
- this.originalLabelColor = this.heartLabel.color.clone();
- }
- }
- }
- /**
- * 从存档加载墙体血量
- */
- private loadWallHealthFromSave() {
- // 直接从SaveDataManager获取当前墙体血量
- this.currentHealth = this.saveDataManager.getWallHealth();
- console.log('[Wall] 从配置加载墙体血量:', this.currentHealth);
-
- // 确保当前血量不超过最大血量(考虑技能加成)
- const maxHealth = this.getMaxHealth();
- if (this.currentHealth > maxHealth) {
- this.currentHealth = maxHealth;
- }
- }
- /**
- * 墙体受到伤害
- */
- public takeDamage(damage: number) {
- if (damage <= 0) return;
- const previousHealth = this.currentHealth;
- this.currentHealth = Math.max(0, this.currentHealth - damage);
-
- // 触发受到伤害事件
- const eventBus = EventBus.getInstance();
- eventBus.emit(GameEvents.WALL_TAKE_DAMAGE, {
- damage: damage,
- previousHealth: previousHealth,
- currentHealth: this.currentHealth
- });
-
- // 触发血量变化事件
- eventBus.emit(GameEvents.WALL_HEALTH_CHANGED, {
- previousHealth: previousHealth,
- currentHealth: this.currentHealth,
- maxHealth: this.getMaxHealth()
- });
-
- // 更新血量显示
- this.updateHealthDisplay();
-
- // 播放受伤动画效果
- this.playDamageAnimation();
-
- console.log(`[Wall] 墙体受到伤害: ${damage}, 当前血量: ${this.currentHealth}`);
-
- // 检查墙体是否被摧毁
- if (this.currentHealth <= 0) {
- this.onWallDestroyed();
- }
- }
- /**
- * 墙体被摧毁时的处理
- * 统一与菜单退出的失败处理流程,直接触发GAME_DEFEAT事件
- */
- private onWallDestroyed() {
- console.log('[Wall] 墙体被摧毁,触发游戏失败');
-
- // 埋点:上报 $GameFailure 事件
- this.trackGameFailureEvent();
-
- // 通过事件系统触发墙体被摧毁事件(保留用于其他监听器)
- const eventBus = EventBus.getInstance();
- eventBus.emit(GameEvents.WALL_DESTROYED, {
- finalHealth: this.currentHealth,
- maxHealth: this.getMaxHealth()
- });
-
- // 统一失败处理:直接触发GAME_DEFEAT事件,与菜单退出处理保持一致
- console.log('[Wall] 直接触发GAME_DEFEAT事件,与菜单退出失败处理流程一致');
- eventBus.emit(GameEvents.GAME_DEFEAT);
- }
- /**
- * 更新血量显示
- */
- public updateHealthDisplay() {
- if (this.heartLabel) {
- this.heartLabel.string = Math.floor(this.currentHealth).toString();
- }
- }
-
- /**
- * 播放受伤动画效果
- * 字体变红并伴随缩放动画(先放大后缩回原来的大小)
- */
- private playDamageAnimation(): void {
- if (!this.heartLabelNode || !this.heartLabel) {
- return;
- }
-
- // 停止之前的动画,防止动画冲突
- this.stopDamageAnimation();
-
- // 重置到原始状态
- this.heartLabelNode.setScale(Vec3.ONE);
- if (this.originalLabelColor) {
- this.heartLabel.color = this.originalLabelColor.clone();
- }
-
- // 创建缩放动画:放大到1.3倍再缩小回原始大小
- this.damageAnimationTween = tween(this.heartLabelNode)
- .to(0.15, { scale: new Vec3(1.3, 1.3, 1) }, {
- easing: 'sineOut'
- })
- .to(0.15, { scale: Vec3.ONE }, {
- easing: 'sineIn'
- })
- .call(() => {
- this.damageAnimationTween = null;
- });
-
- // 创建颜色动画:变红后逐渐恢复原色
- if (this.originalLabelColor) {
- const redColor = Color.RED.clone();
- this.colorAnimationTween = tween(this.heartLabel)
- .to(0.1, { color: redColor }, {
- easing: 'sineOut'
- })
- .delay(0.2) // 保持红色一段时间
- .to(0.5, { color: this.originalLabelColor }, {
- easing: 'sineIn'
- })
- .call(() => {
- this.colorAnimationTween = null;
- });
- }
-
- // 启动动画
- if (this.damageAnimationTween) {
- this.damageAnimationTween.start();
- }
- if (this.colorAnimationTween) {
- this.colorAnimationTween.start();
- }
-
- console.log('[Wall] 播放受伤动画效果');
- }
-
- /**
- * 停止受伤动画
- */
- private stopDamageAnimation(): void {
- if (this.damageAnimationTween) {
- this.damageAnimationTween.stop();
- this.damageAnimationTween = null;
- }
-
- if (this.colorAnimationTween) {
- this.colorAnimationTween.stop();
- this.colorAnimationTween = null;
- }
-
- // 恢复原始状态
- if (this.heartLabelNode) {
- Tween.stopAllByTarget(this.heartLabelNode);
- this.heartLabelNode.setScale(Vec3.ONE);
- }
-
- if (this.heartLabel && this.originalLabelColor) {
- Tween.stopAllByTarget(this.heartLabel);
- this.heartLabel.color = this.originalLabelColor.clone();
- }
- }
- /**
- * 设置墙体血量
- */
- public setHealth(health: number) {
- const previousHealth = this.currentHealth;
- this.currentHealth = Math.max(0, health);
-
- // 如果血量发生变化,触发血量变化事件
- if (previousHealth !== this.currentHealth) {
- const eventBus = EventBus.getInstance();
- eventBus.emit(GameEvents.WALL_HEALTH_CHANGED, {
- previousHealth: previousHealth,
- currentHealth: this.currentHealth,
- maxHealth: this.getMaxHealth()
- });
- }
-
- this.updateHealthDisplay();
- }
- /**
- * 获取当前墙体血量
- */
- public getCurrentHealth(): number {
- return this.currentHealth;
- }
- /**
- * 获取最大血量(基于当前等级和技能加成)
- */
- public getMaxHealth(): number {
- const currentLevel = this.getCurrentWallLevel();
- const baseMaxHealth = this.getWallHealthByLevel(currentLevel);
-
- // 应用治疗技能的最大血量加成
- const skillManager = SkillManager.getInstance();
- if (skillManager) {
- const healSkillLevel = skillManager.getSkillLevel('heal');
- const healthBonus = SkillManager.getHealSkillHealthBonus(healSkillLevel);
- return Math.floor(baseMaxHealth * (1 + healthBonus));
- }
-
- return baseMaxHealth;
- }
- /**
- * 根据等级获取墙体血量
- */
- public getWallHealthByLevel(level: number): number {
- // 使用本地配置
- return this.wallHpMap[level] || (100 + (level - 1) * 200);
- }
- /**
- * 获取当前墙壁等级
- */
- public getCurrentWallLevel(): number {
- return this.saveDataManager?.getWallLevel() || 1;
- }
- /**
- * 恢复墙体血量
- */
- public heal(amount: number) {
- const previousHealth = this.currentHealth;
- const maxHealth = this.getMaxHealth();
- this.currentHealth = Math.min(maxHealth, this.currentHealth + amount);
-
- // 如果血量发生变化,触发血量变化事件
- if (previousHealth !== this.currentHealth) {
- const eventBus = EventBus.getInstance();
- eventBus.emit(GameEvents.WALL_HEALTH_CHANGED, {
- previousHealth: previousHealth,
- currentHealth: this.currentHealth,
- maxHealth: maxHealth,
- healAmount: this.currentHealth - previousHealth
- });
- }
-
- this.updateHealthDisplay();
- }
- /**
- * 重置墙体血量到满血
- */
- public resetToFullHealth() {
- this.currentHealth = this.getMaxHealth();
- this.updateHealthDisplay();
- }
- /**
- * 获取血量百分比
- */
- public getHealthPercentage(): number {
- const maxHealth = this.getMaxHealth();
- return maxHealth > 0 ? this.currentHealth / maxHealth : 0;
- }
- /**
- * 检查墙体是否存活
- */
- public isAlive(): boolean {
- return this.currentHealth > 0;
- }
-
- /**
- * 设置事件监听器
- */
- private setupEventListeners() {
- const eventBus = EventBus.getInstance();
-
- // 监听重置墙体血量事件
- eventBus.on(GameEvents.RESET_WALL_HEALTH, this.onResetWallHealthEvent, this);
-
- // 监听墙体血量变化事件(用于升级后更新显示)
- eventBus.on(GameEvents.WALL_HEALTH_CHANGED, this.onWallHealthChangedEvent, this);
-
- // 监听敌人攻击墙体事件(统一事件处理)
- eventBus.on(GameEvents.ENEMY_DAMAGE_WALL, this.onEnemyDamageWallEvent, this);
- }
-
- /**
- * 处理重置墙体血量事件
- */
- private onResetWallHealthEvent() {
- console.log('[Wall] 接收到重置墙体血量事件,重置到满血');
- this.resetToFullHealth();
- }
-
- /**
- * 处理墙体血量变化事件(用于升级后更新)
- */
- private onWallHealthChangedEvent(eventData?: any) {
- // 只有在特定情况下才重新加载存档数据,避免覆盖受伤后的血量
- // 如果事件数据包含isUpgrade标志,说明是升级触发的,需要重新加载
- if (eventData && eventData.isUpgrade) {
- console.log('[Wall] 接收到墙体升级事件,重新加载血量数据');
- this.loadWallHealthFromSave();
- this.updateHealthDisplay();
- }
- // 其他情况(如受伤、治疗)不需要重新加载存档,血量已经在相应方法中更新
- }
-
- /**
- * 处理敌人攻击墙体事件(统一事件处理)
- */
- private onEnemyDamageWallEvent(eventData: { damage: number, source: Node }) {
- if (eventData && eventData.damage > 0) {
- console.log(`[Wall] 接收到敌人攻击墙体事件,伤害: ${eventData.damage}, 来源: ${eventData.source?.name || '未知'}`);
- this.takeDamage(eventData.damage);
- }
- }
-
- /**
- * 设置技能监听器
- */
- private setupSkillListeners() {
- const skillManager = SkillManager.getInstance();
- if (skillManager) {
- // 监听治疗技能变化
- skillManager.onSkillChanged('heal', this.onHealSkillChanged.bind(this));
- }
- }
-
- /**
- * 治疗技能变化回调
- */
- private onHealSkillChanged(skillId: string, level: number) {
- console.log(`[Wall] 治疗技能等级变化: ${level}`);
-
- // 技能升级时,墙体最大血量可能增加,需要更新显示
- this.updateHealthDisplay();
-
- // 如果当前血量低于新的最大血量,可以考虑给予一些额外治疗
- // 这里暂时不做额外处理,因为SkillSelectionController已经处理了即时治疗
- }
-
- /**
- * 清理技能监听器
- */
- private cleanupSkillListeners() {
- const skillManager = SkillManager.getInstance();
- if (skillManager) {
- skillManager.offSkillChanged('heal', this.onHealSkillChanged.bind(this));
- }
- }
-
- /**
- * 上报游戏失败埋点事件
- */
- private trackGameFailureEvent() {
- try {
- // 获取游戏时长(从 IN_game 管理器获取)
- const inGameNode = find('IN_game');
- let gameDuration = 0;
- if (inGameNode) {
- const inGameManager = inGameNode.getComponent('InGameManager');
- if (inGameManager && typeof inGameManager['getGameDuration'] === 'function') {
- gameDuration = Math.floor(inGameManager['getGameDuration']() / 1000); // 转换为秒
- }
- }
-
- // 获取当前关卡信息
- const currentLevel = this.saveDataManager?.getCurrentLevel() || 1;
-
- // 构建埋点数据
- const failureData = {
- $event_duration: gameDuration,
- $section_id: currentLevel,
- $section_name: `Level ${currentLevel}`,
- $section_type: "主线关卡" // 目前项目中所有关卡都是主线关卡
- };
-
- // 上报埋点
- AnalyticsManager.getInstance().trackGameFailure(failureData);
-
- console.log('[Wall] $GameFailure 埋点已上报:', failureData);
- } catch (error) {
- console.error('[Wall] 上报 $GameFailure 埋点时发生错误:', error);
- }
- }
-
- onDestroy() {
- // 清理事件监听
- const eventBus = EventBus.getInstance();
- eventBus.off(GameEvents.RESET_WALL_HEALTH, this.onResetWallHealthEvent, this);
- eventBus.off(GameEvents.WALL_HEALTH_CHANGED, this.onWallHealthChangedEvent, this);
- eventBus.off(GameEvents.ENEMY_DAMAGE_WALL, this.onEnemyDamageWallEvent, this);
-
- this.cleanupSkillListeners();
-
- // 清理动画资源
- this.stopDamageAnimation();
- }
- }
|