import { _decorator, Component, Node, Vec3, Prefab, instantiate, Collider2D, Contact2DType, IPhysics2DContact, math } from 'cc'; import { BulletInitData, WeaponConfig, BulletLifecycleConfig } from './BulletTypes'; import { ConfigLoader } from './ConfigLoader'; import { BulletCount } from './effects/BulletCount'; import { BulletTrajectory as BCBulletTrajectory } from './effects/BulletTrajectory'; import { BulletHitEffect as BCBulletHitEffect } from './effects/BulletHitEffect'; import { BulletLifecycle as BCBulletLifecycle } from './effects/BulletLifecycle'; import { BulletTrailController } from './BulletTrailController'; const { ccclass } = _decorator; /** 可移植的统一子弹控制器 */ @ccclass('WeaponBulletController') export class WeaponBulletController extends Component { private trajectory: BCBulletTrajectory | null = null; private hitEffect: BCBulletHitEffect | null = null; private lifecycle: BCBulletLifecycle | null = null; private trail: BulletTrailController | null = null; private weaponConfig: WeaponConfig | null = null; private isInitialized = false; // 图片“上为前进方向”的偏移修正(度) private readonly _orientationOffsetByWeapon: Record = { sharp_carrot: -90, hot_pepper: -90, okra_missile: -90, mace_club: -90 }; private getOrientationOffset(): number { const id = this.weaponConfig?.id; if (!id) return 0; return this._orientationOffsetByWeapon[id] ?? 0; } public init(initData: BulletInitData) { // 装配配置 this.weaponConfig = initData.weaponConfig ?? ConfigLoader.getWeaponConfig(initData.weaponId); if (!this.weaponConfig) { this.node.destroy(); return; } // 计算方向 const direction = (initData.direction?.clone() ?? new Vec3(1, 0, 0)).normalize(); // 弹道 this.trajectory = this.getComponent(BCBulletTrajectory) || this.addComponent(BCBulletTrajectory); const trajCfg = { ...this.weaponConfig.bulletConfig.trajectory, speed: this.weaponConfig.stats.bulletSpeed }; this.trajectory.init(trajCfg, direction, initData.firePosition, initData.sourceBlock); // 初始位置略微前移,避免贴边重叠 const spawnOffset = 30; const pos = this.node.position.clone(); this.node.setPosition(pos.x + direction.x * spawnOffset, pos.y + direction.y * spawnOffset); // 命中效果 this.hitEffect = this.getComponent(BCBulletHitEffect) || this.addComponent(BCBulletHitEffect); this.hitEffect.init(this.weaponConfig.bulletConfig.hitEffects); // 生命周期 this.lifecycle = this.getComponent(BCBulletLifecycle) || this.addComponent(BCBulletLifecycle); const lc: BulletLifecycleConfig = { ...this.weaponConfig.bulletConfig.lifecycle }; if (lc.type === 'return_trip' && !lc.maxRange && this.weaponConfig.stats.range) { lc.maxRange = this.weaponConfig.stats.range; } // 透传 pierce/ricochet 初始计数(若命中效果中有) const pierceInit = this.weaponConfig.bulletConfig.hitEffects.find(e => e.type === 'pierce_damage')?.pierceCount ?? lc.penetration ?? 0; const ricochetInit = this.weaponConfig.bulletConfig.hitEffects.find(e => e.type === 'ricochet_damage')?.ricochetCount ?? lc.ricochetCount ?? 0; this.lifecycle.init(lc, initData.firePosition, pierceInit, ricochetInit); // 拖尾(可选) this.trail = this.getComponent(BulletTrailController) || this.addComponent(BulletTrailController); // 旋转对齐外观 const deg = math.toDegree(Math.atan2(direction.y, direction.x)) + this.getOrientationOffset(); const shouldRotate = this.weaponConfig?.bulletConfig?.shouldRotate; const pelletNode = this.node.getChildByName('Pellet'); if (shouldRotate === false && pelletNode) { pelletNode.angle = deg; } else { this.node.angle = deg; } // 启用持续外观对齐/自旋(贴合两种模式) if (this.trajectory) { this.trajectory.setFacingOptions({ shouldRotate: shouldRotate, offsetDeg: this.getOrientationOffset(), targetChildName: pelletNode ? 'Pellet' : undefined, spinSpeedDeg: 720 }); } // 碰撞监听(若存在 Collider2D) const collider = this.getComponent(Collider2D); if (collider) { collider.on(Contact2DType.BEGIN_CONTACT, this.onCollision, this); } this.isInitialized = true; } private onCollision(self: Collider2D, other: Collider2D, contact: IPhysics2DContact | null) { if (!this.isInitialized || !this.hitEffect || !this.lifecycle) return; const result = this.hitEffect.applyOnHit(); this.lifecycle.notifyHit(result); if (this.lifecycle.shouldDestroyNow()) { this.node.destroy(); return; } } } /** * 工具:批量创建子弹(Prefab 或 Node 模板) */ export class BulletEngine { static createBullets(initData: BulletInitData, bulletTemplate: Prefab | Node): Node[] { // 获取配置 const config = initData.weaponConfig ?? ConfigLoader.getWeaponConfig(initData.weaponId); if (!config) return []; const dir = (initData.direction?.clone() ?? new Vec3(1, 0, 0)).normalize(); const spawns = BulletCount.calculateSpawns(config.bulletConfig.count, initData.firePosition, dir); const nodes: Node[] = []; for (const s of spawns) { const node = instantiate(bulletTemplate) as Node; const ctrl = node.getComponent(WeaponBulletController) || node.addComponent(WeaponBulletController); // 处理延迟:先禁用,延时后启用 if (s.delayMs > 0) { node.active = false; ctrl.init({ ...initData, firePosition: s.position, direction: s.direction, weaponConfig: config }); nodes.push(node); setTimeout(() => { if (node && node.isValid) node.active = true; }, s.delayMs); } else { ctrl.init({ ...initData, firePosition: s.position, direction: s.direction, weaponConfig: config }); nodes.push(node); } } return nodes; } }