import { _decorator, Component, Node, Vec3, find, instantiate, Prefab, UITransform, resources, sp, CircleCollider2D } from 'cc'; import { BulletTrajectory } from './BulletTrajectory'; import { HitEffectConfig } from '../../Core/ConfigManager'; import { PersistentSkillManager } from '../../FourUI/SkillSystem/PersistentSkillManager'; import { BurnEffect } from './BurnEffect'; import { GroundBurnArea } from './GroundBurnArea'; import { GroundBurnAreaManager } from './GroundBurnAreaManager'; import { WeaponBullet } from '../WeaponBullet'; import EventBus, { GameEvents } from '../../Core/EventBus'; import { Audio } from '../../AudioManager/AudioManager'; const { ccclass, property } = _decorator; /** * 命中效果控制器 * 负责处理可叠加的命中效果 */ // 接口定义已移至ConfigManager.ts中的HitEffectConfig export interface HitResult { shouldDestroy: boolean; // 是否应该销毁子弹 shouldContinue: boolean; // 是否应该继续飞行 shouldRicochet: boolean; // 是否应该弹射 damageDealt: number; // 造成的伤害 } @ccclass('BulletHitEffect') export class BulletHitEffect extends Component { private hitEffects: HitEffectConfig[] = []; private hitCount: number = 0; private pierceCount: number = 0; private ricochetCount: number = 0; private activeBurnAreas: Node[] = []; // 默认特效路径(从WeaponBullet传入) private defaultHitEffectPath: string = ''; private defaultTrailEffectPath: string = ''; private defaultBurnEffectPath: string = ''; public setDefaultEffects(hit: string | null, trail?: string | null, burn?: string | null) { if (hit) this.defaultHitEffectPath = hit; if (trail) this.defaultTrailEffectPath = trail; if (burn) this.defaultBurnEffectPath = burn; } /** * 初始化命中效果 */ public init(effects: HitEffectConfig[]) { // 按优先级排序 this.hitEffects = [...effects].sort((a, b) => a.priority - b.priority); } /** * 处理命中事件 */ public processHit(hitNode: Node, contactPos: Vec3): HitResult { console.log(`[BulletHitEffect] 处理命中 - 目标: ${hitNode.name}, 位置: ${contactPos}, 效果数量: ${this.hitEffects.length}`); this.hitCount++; const result: HitResult = { shouldDestroy: false, shouldContinue: false, shouldRicochet: false, damageDealt: 0 }; // 按优先级处理所有效果 for (const effect of this.hitEffects) { const effectResult = this.processEffect(effect, hitNode, contactPos); // 累积伤害 result.damageDealt += effectResult.damageDealt; // 处理控制逻辑(OR逻辑,任何一个效果要求的行为都会执行) if (effectResult.shouldDestroy) result.shouldDestroy = true; if (effectResult.shouldContinue) result.shouldContinue = true; if (effectResult.shouldRicochet) result.shouldRicochet = true; } // 逻辑优先级:销毁 > 弹射 > 继续 if (result.shouldDestroy) { result.shouldContinue = false; result.shouldRicochet = false; } else if (result.shouldRicochet) { result.shouldContinue = false; } return result; } /** * 处理单个效果 */ private processEffect(effect: HitEffectConfig, hitNode: Node, contactPos: Vec3): HitResult { const result: HitResult = { shouldDestroy: false, shouldContinue: false, shouldRicochet: false, damageDealt: 0 }; switch (effect.type) { case 'normal_damage': result.damageDealt = this.processNormalDamage(effect, hitNode); result.shouldDestroy = true; break; case 'pierce_damage': result.damageDealt = this.processPierceDamage(effect, hitNode); break; case 'explosion': result.damageDealt = this.processExplosion(effect, contactPos); result.shouldDestroy = true; break; case 'ground_burn': console.log(`[BulletHitEffect] 触发灼烧效果处理`); this.processGroundBurn(effect, hitNode); break; case 'ricochet_damage': result.damageDealt = this.processRicochetDamage(effect, hitNode); result.shouldRicochet = this.ricochetCount < (effect.ricochetCount || 0); break; } return result; } /** * 处理普通伤害 */ private processNormalDamage(effect: any, hitNode: Node): number { // 使用WeaponBullet的最终伤害值而不是配置中的基础伤害 const weaponBullet = this.getComponent(WeaponBullet); const damage = weaponBullet ? weaponBullet.getFinalDamage() : (effect.damage || 0); console.log(`[BulletHitEffect] 普通伤害处理 - 配置伤害: ${effect.damage || 0}, 最终伤害: ${damage}`); this.damageEnemy(hitNode, damage); this.spawnHitEffect(hitNode.worldPosition); return damage; } /** * 处理穿透伤害 */ private processPierceDamage(effect: HitEffectConfig, hitNode: Node): number { // 使用WeaponBullet的最终伤害值而不是配置中的基础伤害 const weaponBullet = this.getComponent(WeaponBullet); const damage = weaponBullet ? weaponBullet.getFinalDamage() : (effect.damage || 0); console.log(`[BulletHitEffect] 穿透伤害处理 - 配置伤害: ${effect.damage || 0}, 最终伤害: ${damage}`); this.damageEnemy(hitNode, damage); this.spawnHitEffect(hitNode.worldPosition); this.pierceCount++; return damage; } /** * 处理爆炸效果 */ private processExplosion(effect: HitEffectConfig, position: Vec3): number { // 使用WeaponBullet的最终伤害值而不是配置中的基础伤害 const weaponBullet = this.getComponent(WeaponBullet); const explosionDamage = weaponBullet ? weaponBullet.getFinalDamage() : (effect.damage || 0); console.log(`[BulletHitEffect] 爆炸伤害处理 - 配置伤害: ${effect.damage || 0}, 最终伤害: ${explosionDamage}`); const scheduleExplosion = () => { // 播放爆炸音效 this.playAttackSound(); // 生成爆炸特效 this.spawnExplosionEffect(position); // 对范围内敌人造成伤害 const damage = this.damageEnemiesInRadius(position, effect.radius, explosionDamage); return damage; }; if (effect.delay > 0) { this.scheduleOnce(scheduleExplosion, effect.delay); return 0; // 延迟爆炸,当前不造成伤害 } else { return scheduleExplosion(); } } /** * 处理地面燃烧区域效果 */ private processGroundBurn(effect: HitEffectConfig, hitNode: Node) { console.log(`[BulletHitEffect] processGroundBurn 被调用 - 创建地面燃烧区域`); // 获取地面燃烧区域管理器 const burnAreaManager = GroundBurnAreaManager.getInstance(); if (!burnAreaManager) { console.error('[BulletHitEffect] 无法获取GroundBurnAreaManager实例'); return; } // 获取子弹的世界位置作为燃烧区域中心 const burnPosition = this.node.worldPosition.clone(); // 如果命中的是敌人,使用敌人的位置 if (this.isEnemyNode(hitNode)) { burnPosition.set(hitNode.worldPosition); } console.log(`[BulletHitEffect] 在位置 (${burnPosition.x.toFixed(1)}, ${burnPosition.y.toFixed(1)}) 创建地面燃烧区域 - 持续时间: ${effect.duration}秒, 伤害: ${effect.damage}, 间隔: ${effect.tickInterval}秒`); try { // 获取武器子弹组件 const weaponBullet = this.getComponent(WeaponBullet); // 通过管理器创建燃烧区域 const burnAreaNode = burnAreaManager.createGroundBurnArea( burnPosition, effect, weaponBullet, this.defaultBurnEffectPath || this.defaultTrailEffectPath ); if (burnAreaNode) { // 将燃烧区域添加到活跃列表中 this.activeBurnAreas.push(burnAreaNode); console.log(`[BulletHitEffect] 地面燃烧区域已通过管理器创建`); } } catch (error) { console.error('[BulletHitEffect] 通过管理器创建地面燃烧区域失败:', error); } } /** * 处理弹射伤害 */ private processRicochetDamage(effect: HitEffectConfig, hitNode: Node): number { // 使用WeaponBullet的最终伤害值而不是配置中的基础伤害 const weaponBullet = this.getComponent(WeaponBullet); const damage = weaponBullet ? weaponBullet.getFinalDamage() : (effect.damage || 0); console.log(`[BulletHitEffect] 弹射伤害处理 - 配置伤害: ${effect.damage || 0}, 最终伤害: ${damage}`); this.damageEnemy(hitNode, damage); if (this.ricochetCount < effect.ricochetCount) { this.ricochetCount++; // 计算弹射方向 this.calculateRicochetDirection(effect.ricochetAngle); } return damage; } /** * 计算弹射方向 */ private calculateRicochetDirection(maxAngle: number) { const trajectory = this.getComponent(BulletTrajectory); if (!trajectory) return; // 获取当前速度方向 const currentVel = trajectory.getCurrentVelocity(); // 随机弹射角度 const angleRad = (Math.random() - 0.5) * maxAngle * Math.PI / 180; const cos = Math.cos(angleRad); const sin = Math.sin(angleRad); // 应用旋转 const newDirection = new Vec3( currentVel.x * cos - currentVel.y * sin, currentVel.x * sin + currentVel.y * cos, 0 ).normalize(); // 使用弹道组件的changeDirection方法 trajectory.changeDirection(newDirection); } /** * 对单个敌人造成伤害 */ private damageEnemy(enemyNode: Node, damage: number) { if (!this.isEnemyNode(enemyNode)) return; // 播放攻击音效 this.playAttackSound(); // 计算暴击伤害和暴击状态 const damageResult = this.calculateCriticalDamage(damage); // 使用applyDamageToEnemy方法,通过EventBus发送伤害事件 this.applyDamageToEnemy(enemyNode, damageResult.damage, damageResult.isCritical); } /** * 计算暴击伤害 */ private calculateCriticalDamage(baseDamage: number): { damage: number, isCritical: boolean } { // 获取武器子弹组件来计算暴击 const weaponBullet = this.getComponent(WeaponBullet); if (!weaponBullet) { // 如果没有WeaponBullet组件,使用基础暴击率10% const critChance = 0.1; const isCritical = Math.random() < critChance; if (isCritical) { // 暴击伤害 = 基础伤害 × (1 + 暴击伤害倍率),默认暴击倍率100% const critDamage = Math.ceil((baseDamage * (1 + 1.0)) * 10) / 10; // 向上取整到一位小数 this.showCriticalHitEffect(); console.log(`[BulletHitEffect] 暴击!基础伤害: ${baseDamage}, 暴击伤害: ${critDamage}, 暴击率: ${(critChance * 100).toFixed(1)}%`); return { damage: critDamage, isCritical: true }; } return { damage: baseDamage, isCritical: false }; } // 获取暴击率 const critChance = weaponBullet.getCritChance(); // 检查是否触发暴击 const isCritical = Math.random() < critChance; if (isCritical) { // 获取暴击伤害倍率(从技能系统) const critDamageMultiplier = this.getCritDamageMultiplier(); // 暴击伤害 = 基础伤害 × (1 + 暴击伤害倍率) const critDamage = Math.ceil((baseDamage * (1 + critDamageMultiplier)) * 10) / 10; // 向上取整到一位小数 // 显示暴击特效 this.showCriticalHitEffect(); console.log(`[BulletHitEffect] 暴击!基础伤害: ${baseDamage}, 暴击倍率: ${(critDamageMultiplier * 100).toFixed(1)}%, 暴击伤害: ${critDamage}, 暴击率: ${(critChance * 100).toFixed(1)}%`); return { damage: critDamage, isCritical: true }; } return { damage: baseDamage, isCritical: false }; } /** * 获取暴击伤害倍率 */ private getCritDamageMultiplier(): number { // 从局外技能系统获取暴击伤害加成 const persistentSkillManager = PersistentSkillManager.getInstance(); if (persistentSkillManager) { const bonuses = persistentSkillManager.getSkillBonuses(); // critDamageBonus是百分比值,需要转换为倍率 // 暴击伤害倍率 = 基础100% + 技能加成百分比 return 1.0 + (bonuses.critDamageBonus || 0) / 100; } // 默认暴击倍率100%(即2倍伤害) return 1.0; } /** * 播放攻击音效 */ private playAttackSound() { try { // 获取武器子弹组件 const weaponBullet = this.getComponent(WeaponBullet); if (!weaponBullet) { console.log('[BulletHitEffect] 未找到WeaponBullet组件,无法播放攻击音效'); return; } // 获取武器配置 const weaponConfig = weaponBullet.getWeaponConfig(); if (!weaponConfig || !weaponConfig.visualConfig || !weaponConfig.visualConfig.attackSound) { console.log('[BulletHitEffect] 武器配置中未找到attackSound,跳过音效播放'); return; } // 播放攻击音效 const attackSoundPath = weaponConfig.visualConfig.attackSound; console.log(`[BulletHitEffect] 播放攻击音效: ${attackSoundPath}`); Audio.playWeaponSound(attackSoundPath); } catch (error) { console.error('[BulletHitEffect] 播放攻击音效时出错:', error); } } /** * 显示暴击特效 */ private showCriticalHitEffect() { // 这里可以添加暴击特效,比如特殊的粒子效果或音效 // 暂时只在控制台输出,后续可以扩展 console.log('[BulletHitEffect] 暴击特效触发!'); } /** * 对范围内敌人造成伤害 */ private damageEnemiesInRadius(center: Vec3, radius: number, damage: number): number { const enemyContainer = find('Canvas/GameLevelUI/enemyContainer'); if (!enemyContainer) return 0; let totalDamage = 0; const enemies = enemyContainer.children.filter(child => child.active && this.isEnemyNode(child) ); // 对于持续伤害(如地面灼烧),直接使用传入的伤害值 // 对于爆炸伤害,使用WeaponBullet的最终伤害值 const baseDamage = damage; console.log(`[BulletHitEffect] 范围伤害 - 中心位置: (${center.x.toFixed(1)}, ${center.y.toFixed(1)}), 半径: ${radius}, 基础伤害: ${baseDamage}`); for (const enemy of enemies) { const distance = Vec3.distance(center, enemy.worldPosition); if (distance <= radius) { // 每个敌人独立计算暴击 const damageResult = this.calculateCriticalDamage(baseDamage); console.log(`[BulletHitEffect] 敌人受到范围伤害 - 距离: ${distance.toFixed(1)}, 伤害: ${damageResult.damage}, 暴击: ${damageResult.isCritical}`); // 直接处理伤害,避免重复计算暴击 this.applyDamageToEnemy(enemy, damageResult.damage, damageResult.isCritical); totalDamage += damageResult.damage; } } return totalDamage; } /** * 直接对敌人应用伤害(不进行暴击计算) */ private applyDamageToEnemy(enemyNode: Node, damage: number, isCritical: boolean = false) { console.log(`[BulletHitEffect] 通过EventBus发送伤害事件: ${damage}, 暴击: ${isCritical}, 敌人节点: ${enemyNode.name}`); if (!this.isEnemyNode(enemyNode)) { console.log(`[BulletHitEffect] 节点不是敌人,跳过伤害`); return; } // 通过EventBus发送伤害事件 const eventBus = EventBus.getInstance(); const damageData = { enemyNode: enemyNode, damage: damage, isCritical: isCritical, source: 'BulletHitEffect' }; console.log(`[BulletHitEffect] 发送APPLY_DAMAGE_TO_ENEMY事件`, damageData); eventBus.emit(GameEvents.APPLY_DAMAGE_TO_ENEMY, damageData); } /** * 判断是否为敌人节点 */ private isEnemyNode(node: Node): boolean { if (!node || !node.isValid) { return false; } const name = node.name.toLowerCase(); return name.includes('enemy') || name.includes('敌人'); } /** * 生成爆炸特效 */ private spawnExplosionEffect(position: Vec3) { const path = this.defaultHitEffectPath || 'Animation/WeaponTx/tx0004/tx0004'; this.spawnEffect(path, position, false); } /** * 生成灼烧特效 */ private spawnBurnEffect(parent: Node) { const path = this.defaultBurnEffectPath || this.defaultTrailEffectPath || 'Animation/WeaponTx/tx0006/tx0006'; // 使用回调函数处理异步创建的特效节点 this.spawnEffect(path, new Vec3(), true, parent, (effectNode) => { // 将特效节点传递给 BurnEffect 组件,以便在停止时清理 const burnEffect = parent.getComponent(BurnEffect); if (burnEffect && effectNode) { burnEffect.setBurnEffectNode(effectNode); } }); } /** * 生成地面燃烧特效 */ private spawnGroundBurnEffect(parent: Node) { const path = this.defaultBurnEffectPath || this.defaultTrailEffectPath || 'Animation/WeaponTx/tx0006/tx0006'; // 使用回调函数处理异步创建的特效节点 this.spawnEffect(path, new Vec3(), true, parent, (effectNode) => { // 将特效节点传递给 GroundBurnArea 组件,以便在停止时清理 const groundBurnArea = parent.getComponent(GroundBurnArea); if (groundBurnArea && effectNode) { groundBurnArea.setBurnEffectNode(effectNode); } }); } /** * 生成特效 */ private spawnEffect(path: string, worldPos: Vec3, loop = false, parent?: Node, onCreated?: (effectNode: Node) => void): void { if (!path) return; const spawnWithData = (skData: sp.SkeletonData) => { const effectNode = new Node('Effect'); const skeletonComp: sp.Skeleton = effectNode.addComponent(sp.Skeleton); skeletonComp.skeletonData = skData; skeletonComp.setAnimation(0, 'animation', loop); const targetParent = parent || find('Canvas/GameLevelUI/GameArea') || find('Canvas'); if (targetParent) { targetParent.addChild(effectNode); if (parent) { effectNode.setPosition(0, 0, 0); } else { const parentTrans = targetParent.getComponent(UITransform); if (parentTrans) { const localPos = parentTrans.convertToNodeSpaceAR(worldPos); effectNode.position = localPos; } } } if (!loop) { skeletonComp.setCompleteListener(() => { effectNode.destroy(); }); } // 调用回调函数,传递创建的特效节点 if (onCreated) { onCreated(effectNode); } }; // 先尝试直接加载给定路径 resources.load(path, sp.SkeletonData, (err, skData: sp.SkeletonData) => { if (err) { console.warn('加载特效失败:', path, err); return; } spawnWithData(skData); }); } /** * 清理资源 */ onDestroy() { // 不再强制销毁燃烧区域,让它们按照自己的持续时间自然销毁 // 燃烧区域现在由GroundBurnAreaManager统一管理,有自己的生命周期 console.log(`[BulletHitEffect] 子弹销毁,但不会影响已创建的${this.activeBurnAreas.length}个燃烧区域`); // 只清空引用,不销毁燃烧区域节点 this.activeBurnAreas = []; } /** * 获取命中统计 */ public getHitStats() { return { hitCount: this.hitCount, pierceCount: this.pierceCount, ricochetCount: this.ricochetCount }; } /** * 验证配置 */ public static validateConfig(effects: HitEffectConfig[]): boolean { if (!Array.isArray(effects) || effects.length === 0) return false; for (const effect of effects) { if (!effect.type || effect.priority < 0) return false; if (!effect.params) return false; } return true; } private spawnHitEffect(worldPos: Vec3) { const path = this.defaultHitEffectPath; if (path) { this.spawnEffect(path, worldPos, false); } } }