| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- 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);
- }
- }
- }
|