import { _decorator, Component, Node, Vec3, find, instantiate, Prefab, UITransform, resources, sp, CircleCollider2D, Contact2DType, Collider2D, IPhysics2DContact, Animation } 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'; import { BundleLoader } from '../../Core/BundleLoader'; import { EnemyAttackStateManager } from '../EnemyAttackStateManager'; 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[] = []; // 爆炸一次性保护,防止同一子弹重复结算范围伤害 private hasExploded: boolean = false; // 检测范围相关 private detectionCollider: CircleCollider2D | null = null; private detectedEnemies: Set = new Set(); // 锯齿草子弹状态管理 private hasFirstHit: boolean = false; // 是否已经首次击中敌人 private currentTargetEnemy: Node | null = null; // 当前击中的目标敌人 // 默认特效路径(从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); // 重置锯齿草状态 this.resetSawGrassState(); // 重置爆炸状态 this.hasExploded = false; // 检查是否有手动添加的检测范围碰撞器 this.setupDetectionColliderEvents(); } /** * 是否包含爆炸效果 */ public hasExplosionEffect(): boolean { return Array.isArray(this.hitEffects) && this.hitEffects.some(e => e.type === 'explosion'); } /** * 处理命中事件 */ public processHit(hitNode: Node, contactPos: Vec3): HitResult { // 检查是否是弹射后的命中 const isRicochetHit = this.ricochetCount > 0; const hitType = isRicochetHit ? '弹射命中' : '直接命中'; // 锯齿草子弹首次击中逻辑 const weaponBullet = this.getComponent(WeaponBullet); const weaponInfo = weaponBullet ? weaponBullet.getWeaponInfo() : null; const weaponId = weaponInfo ? weaponInfo.getWeaponId() : null; if (weaponId === 'saw_grass' && !this.hasFirstHit && this.isEnemyNode(hitNode)) { this.hasFirstHit = true; this.currentTargetEnemy = hitNode; } 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': // 若已爆炸则不再重复结算伤害,只标记销毁 if (!this.hasExploded) { result.damageDealt = this.processExplosion(effect, contactPos); this.hasExploded = true; } result.shouldDestroy = true; break; case 'ground_burn': this.processGroundBurn(effect, hitNode); break; case 'ricochet_damage': // 先判断是否可以弹射(在递增ricochetCount之前) const canRicochet = this.ricochetCount < (effect.ricochetCount || 0); result.damageDealt = this.processRicochetDamage(effect, hitNode); result.shouldRicochet = canRicochet; break; } return result; } /** * 处理普通伤害 */ private processNormalDamage(effect: any, hitNode: Node): number { // 使用WeaponBullet的最终伤害值而不是配置中的基础伤害 const weaponBullet = this.getComponent(WeaponBullet); const damage = weaponBullet ? weaponBullet.getFinalDamage() : (effect.damage || 0); 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); 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); const scheduleExplosion = () => { // 播放爆炸音效 this.playAttackSound(); // 生成爆炸特效 this.spawnExplosionEffect(position); // 对范围内敌人造成伤害 const damage = this.damageEnemiesInRadius(position, effect.radius, explosionDamage); return damage; }; // 立即爆炸,不使用任何延迟 return scheduleExplosion(); } /** * 处理地面燃烧区域效果 */ private processGroundBurn(effect: HitEffectConfig, hitNode: Node) { // 获取地面燃烧区域管理器 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); } try { // 获取武器子弹组件 const weaponBullet = this.getComponent(WeaponBullet); // 通过管理器创建燃烧区域 const burnAreaNode = burnAreaManager.createGroundBurnArea( burnPosition, effect, weaponBullet, this.defaultBurnEffectPath || this.defaultTrailEffectPath ); if (burnAreaNode) { // 将燃烧区域添加到活跃列表中 this.activeBurnAreas.push(burnAreaNode); } } 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); this.damageEnemy(hitNode, damage); // 检查是否还能继续弹射 if (this.ricochetCount < effect.ricochetCount) { this.ricochetCount++; // 计算弹射方向 this.calculateRicochetDirection(effect.ricochetAngle); } else { //console.log(`[BulletHitEffect] 弹射次数已达上限,不再弹射`); } return damage; } /** * 计算弹射方向 */ private calculateRicochetDirection(maxAngle: number) { const trajectory = this.getComponent(BulletTrajectory); if (!trajectory) return; // 获取武器子弹组件来确定检测范围 const weaponBullet = this.getComponent(WeaponBullet); const weaponInfo = weaponBullet ? weaponBullet.getWeaponInfo() : null; const weaponId = weaponInfo ? weaponInfo.getWeaponId() : null; const detectionRange = weaponId ? this.getDetectionRange(weaponId) : 500; let nearestEnemy: Node | null = null; // 锯齿草武器使用专门的CircleCollider2D实时追踪逻辑 if (weaponId === 'saw_grass') { nearestEnemy = this.findNearestEnemyForSawGrass(detectionRange); } else { // 其他武器使用原有的实时计算方法 nearestEnemy = this.findNearestEnemy(detectionRange); } let newDirection: Vec3; if (nearestEnemy) { // 如果找到敌人,计算朝向敌人的方向 const bulletPos = this.node.worldPosition; const enemyPos = nearestEnemy.worldPosition; // 计算基础方向(朝向敌人) const baseDirection = enemyPos.clone().subtract(bulletPos).normalize(); // 添加随机偏移角度,让弹射不那么精确 const angleRad = (Math.random() - 0.5) * maxAngle * Math.PI / 180; const cos = Math.cos(angleRad); const sin = Math.sin(angleRad); // 应用旋转偏移 newDirection = new Vec3( baseDirection.x * cos - baseDirection.y * sin, baseDirection.x * sin + baseDirection.y * cos, 0 ).normalize(); const weaponType = weaponId === 'saw_grass' ? '锯齿草CircleCollider2D' : '实时计算'; } else { // 如果没有找到敌人,使用随机方向(保持原有逻辑) const currentVel = trajectory.getCurrentVelocity(); const angleRad = (Math.random() - 0.5) * maxAngle * Math.PI / 180; const cos = Math.cos(angleRad); const sin = Math.sin(angleRad); newDirection = new Vec3( currentVel.x * cos - currentVel.y * sin, currentVel.x * sin + currentVel.y * cos, 0 ).normalize(); const weaponType = weaponId === 'saw_grass' ? '锯齿草CircleCollider2D' : '实时计算'; } // 使用弹道组件的changeDirection方法 trajectory.changeDirection(newDirection); } /** * 对单个敌人造成伤害 */ private damageEnemy(enemyNode: Node, damage: number) { if (!this.isEnemyNode(enemyNode)) return; // 检查敌人是否处于漂移状态,如果是则跳过伤害 const enemyInstance = enemyNode.getComponent('EnemyInstance') as any; if (enemyInstance && enemyInstance.isDrifting()) { console.log(`[BulletHitEffect] 敌人 ${enemyNode.name} 正在漂移中,跳过伤害处理`); 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组件,使用基础暴击率0% const critChance = 0; const isCritical = Math.random() < critChance; if (isCritical) { // 暴击伤害 = 基础伤害 × (1 + 暴击伤害倍率),默认暴击倍率100% const critDamage = Math.round(baseDamage * (1 + 1.0)); // 四舍五入为整数 this.showCriticalHitEffect(); 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.round(baseDamage * (1 + critDamageMultiplier)); // 四舍五入为整数 // 显示暴击特效 this.showCriticalHitEffect(); 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; 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 attackStateManager = EnemyAttackStateManager.getInstance(); const enemies = enemyContainer.children.filter(child => { if (!child.active || !this.isEnemyNode(child)) return false; // 仅对可攻击敌人造成范围伤害 return attackStateManager ? attackStateManager.isEnemyAttackable(child) : true; }); // 对于持续伤害(如地面灼烧),直接使用传入的伤害值 // 对于爆炸伤害,使用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 enemyInstance = enemy.getComponent('EnemyInstance') as any; if (enemyInstance && enemyInstance.isDrifting()) { console.log(`[BulletHitEffect] 敌人 ${enemy.name} 正在漂移中,跳过范围伤害`); continue; // 漂移状态下不受到范围伤害 } // 每个敌人独立计算暴击 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; } // 检查是否可攻击(例如隐身状态不可攻击) const attackStateManager = EnemyAttackStateManager.getInstance(); if (attackStateManager && !attackStateManager.isEnemyAttackable(enemyNode)) { console.log(`[BulletHitEffect] 敌人不可攻击(隐身/状态),跳过伤害: ${enemyNode.name}`); 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; } // 检查是否为EnemySprite子节点 if (node.name === 'EnemySprite' && node.parent) { return node.parent.getComponent('EnemyInstance') !== null; } // 兼容旧的敌人检测逻辑 const name = node.name.toLowerCase(); return name.includes('enemy') || name.includes('敌人'); } /** * 获取武器的敌人检测范围 */ private getDetectionRange(weaponId: string): number { // 根据武器类型设置不同的检测范围 const detectionRanges: { [key: string]: number } = { 'saw_grass': 500, // 锯齿草:500范围智能弹射 'sharp_carrot': 300, // 尖胡萝卜:较短范围 'pea_shooter': 200, // 毛豆射手:基础范围 // 可以根据需要添加更多武器的检测范围 }; return detectionRanges[weaponId] || 300; // 默认300范围 } /** * 寻找最近的敌人(在指定范围内) */ /** * 锯齿草武器专用的敌人查找方法 - 基于CircleCollider2D检测并实时追踪 */ private findNearestEnemyForSawGrass(maxRange: number = 500): Node | null { // 如果还没有首次击中敌人,不使用CircleCollider检测到的敌人 if (!this.hasFirstHit) { console.log(`⏳ [锯齿草弹射] 尚未首次击中敌人,暂不启用CircleCollider追踪`); return null; } // 清理无效的敌人节点 const invalidEnemies = new Set(); for (const enemy of this.detectedEnemies) { if (!enemy || !enemy.isValid || !enemy.active) { invalidEnemies.add(enemy); } } // 移除无效敌人 for (const invalidEnemy of invalidEnemies) { this.detectedEnemies.delete(invalidEnemy); } if (this.detectedEnemies.size === 0) { return null; } const currentPos = this.node.worldPosition; const attackStateManager = EnemyAttackStateManager.getInstance(); let nearestEnemy: Node | null = null; let nearestDistance = Infinity; // 实时计算每个检测到敌人的当前位置,排除当前目标敌人 for (const enemy of this.detectedEnemies) { if (enemy && enemy.isValid && enemy.active) { // 排除当前击中的目标敌人 if (this.currentTargetEnemy && enemy === this.currentTargetEnemy) { continue; } // 排除不可攻击或漂移中的敌人 const enemyInstance = enemy.getComponent('EnemyInstance') as any; const isDrifting = enemyInstance && typeof enemyInstance.isDrifting === 'function' ? enemyInstance.isDrifting() : false; const isAttackable = attackStateManager ? attackStateManager.isEnemyAttackable(enemy) : true; if (!isAttackable || isDrifting) { continue; } // 获取敌人的实时位置 const enemyCurrentPos = enemy.worldPosition; const distance = Vec3.distance(currentPos, enemyCurrentPos); // 检查是否在范围内且是最近的 if (distance <= maxRange && distance < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } } if (nearestEnemy) { } else { console.log(`❌ [锯齿草弹射] 检测到的敌人都超出范围或无效,或都是当前目标敌人`); } return nearestEnemy; } /** * 通用的敌人查找方法 - 用于非锯齿草武器 */ private findNearestEnemy(maxRange: number = 500): Node | null { // 回退到原有的实时计算方法(用于非锯齿草武器) const enemyContainer = find('Canvas/GameLevelUI/enemyContainer'); if (!enemyContainer) return null; const attackStateManager = EnemyAttackStateManager.getInstance(); const enemies = enemyContainer.children.filter(child => { if (!child.active) return false; const nameLower = child.name.toLowerCase(); if (nameLower.includes('enemy') || nameLower.includes('敌人')) return true; if (child.getComponent('EnemyInstance')) return true; return false; }); if (enemies.length === 0) return null; let nearest: Node = null; let nearestDist = Infinity; const bulletPos = this.node.worldPosition; for (const enemy of enemies) { // 排除不可攻击或漂移中的敌人 const enemyInstance = enemy.getComponent('EnemyInstance') as any; const isDrifting = enemyInstance && typeof enemyInstance.isDrifting === 'function' ? enemyInstance.isDrifting() : false; const isAttackable = attackStateManager ? attackStateManager.isEnemyAttackable(enemy) : true; if (!isAttackable || isDrifting) { continue; } const dist = Vec3.distance(bulletPos, enemy.worldPosition); // 只考虑在检测范围内的敌人 if (dist <= maxRange && dist < nearestDist) { nearestDist = dist; nearest = enemy; } } return nearest; } /** * 生成爆炸特效 */ 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; // 使用BundleLoader加载Animation Bundle中的特效资源 // 转换路径格式,去除"Animation/"前缀 const bundlePath = path.replace(/^Animation\//, ''); const bundleLoader = BundleLoader.getInstance(); // 使用loadSkeletonData加载骨骼动画资源,就像敌人动画那样 BundleLoader.loadSkeletonData(bundlePath).then((skData) => { if (!skData) { console.warn('加载特效失败: 资源为空', path); return; } // 创建特效节点 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/enemyContainer') || 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(() => { if (effectNode && effectNode.isValid) { effectNode.destroy(); } }); } // 调用回调函数,传递创建的特效节点 if (onCreated) { onCreated(effectNode); } }).catch((err) => { console.warn('加载特效失败:', path, err); }); } /** * 清理资源 */ onDestroy() { // 不再强制销毁燃烧区域,让它们按照自己的持续时间自然销毁 // 燃烧区域现在由GroundBurnAreaManager统一管理,有自己的生命周期 // 只清空引用,不销毁燃烧区域节点 this.activeBurnAreas = []; // 清理检测碰撞器事件监听 if (this.detectionCollider) { this.detectionCollider.off(Contact2DType.BEGIN_CONTACT, this.onDetectionEnter, this); this.detectionCollider.off(Contact2DType.END_CONTACT, this.onDetectionExit, this); this.detectionCollider = null; } // 清理检测到的敌人集合 this.detectedEnemies.clear(); // 重置锯齿草状态 this.resetSawGrassState(); } /** * 设置检测范围碰撞器事件监听(用于手动添加的CircleCollider2D) */ private setupDetectionColliderEvents() { // 首先在当前节点查找CircleCollider2D组件 let colliders = this.getComponents(CircleCollider2D); // 如果当前节点没有找到,则在子节点中递归查找 if (colliders.length === 0) { colliders = this.getComponentsInChildren(CircleCollider2D); } // 找到用作检测范围的CircleCollider2D(通常是sensor模式的) for (const collider of colliders) { if (collider.sensor) { // 只要是sensor模式的CircleCollider2D就认为是检测范围碰撞器 this.detectionCollider = collider; // 启用碰撞监听 - Cocos Creator 3.8.6正确的事件类型 this.detectionCollider.on(Contact2DType.BEGIN_CONTACT, this.onDetectionEnter, this); this.detectionCollider.on(Contact2DType.END_CONTACT, this.onDetectionExit, this); break; } } if (!this.detectionCollider) { console.log(`[BulletHitEffect] 未找到检测范围碰撞器,将使用实时计算方式`); } } /** * 检测范围进入事件 */ private onDetectionEnter(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { const otherNode = otherCollider.node; if (this.isEnemyNode(otherNode)) { // 仅在可攻击状态下追踪敌人 const attackStateManager = EnemyAttackStateManager.getInstance(); if (attackStateManager && !attackStateManager.isEnemyAttackable(otherNode)) { return; } // 获取武器信息,确认是否为锯齿草武器 const weaponBullet = this.getComponent(WeaponBullet); const weaponInfo = weaponBullet ? weaponBullet.getWeaponInfo() : null; const weaponId = weaponInfo ? weaponInfo.getWeaponId() : null; // 对于锯齿草武器,记录所有检测到的敌人,但在弹射时会根据hasFirstHit状态进行过滤 this.detectedEnemies.add(otherNode); if (weaponId === 'saw_grass') { const hitStatus = this.hasFirstHit ? '已首次击中' : '尚未首次击中'; } } } /** * 检测范围退出事件 */ private onDetectionExit(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { const otherNode = otherCollider.node; if (this.isEnemyNode(otherNode)) { this.detectedEnemies.delete(otherNode); // 获取武器信息,确认是否为锯齿草武器 const weaponBullet = this.getComponent(WeaponBullet); const weaponInfo = weaponBullet ? weaponBullet.getWeaponInfo() : null; const weaponId = weaponInfo ? weaponInfo.getWeaponId() : null; if (weaponId === 'saw_grass') { console.log(`🌿 [锯齿草检测] 锯齿草武器敌人离开: ${otherNode.name}`); } } } /** * 获取命中统计 */ 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); } } /** * 重置锯齿草状态 */ private resetSawGrassState() { this.hasFirstHit = false; this.currentTargetEnemy = null; } }