import { _decorator, Component, Node, Vec2, Vec3, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, find, Prefab, instantiate, UITransform, resources, sp } from 'cc'; import { WeaponBlockExample } from './WeaponBlockExample'; const { ccclass, property } = _decorator; /** * WeaponBullet - 统一的子弹控制器 * * 整合了原 BulletController 的所有功能: * 1. 自动瞄准最近敌人 * 2. 物理属性设置 * 3. 生命周期管理 * 4. 特效系统 * 5. 多种子弹行为 * * 支持的子弹行为: * - explosive: 击中敌人立即销毁并播放hitEffect * - piercing: 击中敌人不销毁,持续飞行 * - ricochet: 弹射子弹,可设置弹射次数 * - boomerang: 回旋镖,延迟后返回 * - area_burn: 区域灼烧效果 * - homing: 导弹追踪 */ export type BulletBehaviour = 'explosive' | 'piercing' | 'ricochet' | 'boomerang' | 'area_burn' | 'homing' | 'normal'; export interface BulletInitData { behavior: BulletBehaviour; speed: number; damage: number; hitEffect?: string; // resources 路径 trailEffect?: string; // resources 路径 lifetime?: number; // 子弹生存时间 ricochetCount?: number; // ricochet 弹射次数 ricochetAngle?: number; // 每次弹射随机角度 (deg) arcHeight?: number; // boomerang returnDelay?: number; burnDuration?: number; // area burn homingDelay?: number; // homing missile homingStrength?: number; // 从 BulletController 迁移的配置 firePosition?: Vec3; // 发射位置(世界坐标) autoTarget?: boolean; // 是否自动瞄准最近敌人 } @ccclass('WeaponBullet') export class WeaponBullet extends Component { // === 基础配置 === @property public speed: number = 300; @property public damage: number = 1; @property public lifetime: number = 5; @property({ tooltip: '击中特效路径(resources 相对路径,例如 Animation/tx/tx0001)' }) public hitEffectPath: string = ''; @property({ tooltip: '拖尾特效路径' }) public trailEffectPath: string = ''; @property({ tooltip: '是否自动瞄准最近敌人' }) public autoTarget: boolean = true; // === 子弹行为配置 === private behavior: BulletBehaviour = 'explosive'; private ricochetLeft = 0; private ricochetAngle = 30; // deg // boomerang private boomerangTimer = 0; private boomerangReturnDelay = 1; private initialDir: Vec3 = new Vec3(); // area burn private burnDuration = 5; // homing private homingDelay = 0.3; private homingStrength = 0.8; private homingTimer = 0; // === 从 BulletController 迁移的属性 === private targetEnemy: Node = null; private rigidBody: RigidBody2D = null; private lifeTimer: number = 0; private direction: Vec3 = new Vec3(1, 0, 0); private firePosition: Vec3 = null; private isInitialized: boolean = false; private needsPhysicsSetup: boolean = false; /** * 初始化子弹配置 */ public init(cfg: BulletInitData) { this.behavior = cfg.behavior || 'explosive'; this.speed = cfg.speed || this.speed; this.damage = cfg.damage || this.damage; this.hitEffectPath = cfg.hitEffect || this.hitEffectPath; this.trailEffectPath = cfg.trailEffect || this.trailEffectPath; this.lifetime = cfg.lifetime !== undefined ? cfg.lifetime : this.lifetime; this.autoTarget = cfg.autoTarget !== undefined ? cfg.autoTarget : this.autoTarget; // 子弹行为配置 if (cfg.ricochetCount !== undefined) this.ricochetLeft = cfg.ricochetCount; if (cfg.ricochetAngle) this.ricochetAngle = cfg.ricochetAngle; if (cfg.returnDelay) this.boomerangReturnDelay = cfg.returnDelay; if (cfg.burnDuration) this.burnDuration = cfg.burnDuration; if (cfg.homingDelay) { this.homingDelay = cfg.homingDelay; this.homingTimer = cfg.homingDelay; } if (cfg.homingStrength) this.homingStrength = cfg.homingStrength; // 发射位置设置 if (cfg.firePosition) { this.setFirePosition(cfg.firePosition); } // 兼容性处理 if (this.behavior === 'normal') this.behavior = 'explosive'; console.log('🔫 WeaponBullet 初始化完成:', { behavior: this.behavior, speed: this.speed, damage: this.damage, autoTarget: this.autoTarget }); } /** * 设置子弹的发射位置(从 BulletController 迁移) * @param position 发射位置(世界坐标) */ public setFirePosition(position: Vec3) { this.firePosition = position.clone(); this.needsPhysicsSetup = true; console.log('🎯 子弹发射位置已设置:', this.firePosition); } /** * 设置特效路径(从 BulletController 迁移) */ public setEffectPaths(hit: string | null, trail?: string | null) { if (hit) this.hitEffectPath = hit; if (trail) this.trailEffectPath = trail; } onLoad() { this.rigidBody = this.getComponent(RigidBody2D); const collider = this.getComponent(Collider2D); if (collider) { collider.on(Contact2DType.BEGIN_CONTACT, this.onHit, this); } } start() { console.log('🚀 WeaponBullet start()开始...'); // 设置生命周期 this.lifeTimer = this.lifetime; // 如果需要设置物理属性,延迟处理 if (this.needsPhysicsSetup) { this.scheduleOnce(() => { this.setupPhysics(); }, 0.05); } else { // 直接初始化 this.setupPhysics(); } // 添加拖尾特效 if (this.behavior === 'piercing' && this.trailEffectPath) { this.attachTrail(); } console.log('✅ WeaponBullet start()完成'); } /** * 设置物理属性(从 BulletController 迁移) */ private setupPhysics() { console.log('🔧 开始设置子弹物理属性...'); // 确保有刚体组件 if (!this.rigidBody) { console.error('❌ 子弹预制体缺少RigidBody2D组件'); return; } const collider = this.getComponent(Collider2D); if (!collider) { console.error('❌ 子弹预制体缺少Collider2D组件'); return; } // 设置物理属性 this.rigidBody.enabledContactListener = true; this.rigidBody.gravityScale = 0; // 不受重力影响 this.rigidBody.linearDamping = 0; // 无阻尼 this.rigidBody.angularDamping = 0; // 无角阻尼 console.log('✅ 物理组件设置成功'); // 设置子弹位置 if (this.firePosition) { this.setPositionInGameArea(); } // 初始化方向和速度 this.initializeDirection(); this.isInitialized = true; console.log('✅ 子弹物理属性设置完成'); } /** * 在GameArea中设置子弹位置(从 BulletController 迁移) */ private setPositionInGameArea() { const gameArea = find('Canvas/GameLevelUI/GameArea'); if (gameArea) { const gameAreaTransform = gameArea.getComponent(UITransform); if (gameAreaTransform) { const localPos = gameAreaTransform.convertToNodeSpaceAR(this.firePosition); this.node.position = localPos; console.log('✅ 子弹位置已设置:', localPos); } } } /** * 初始化子弹方向(从 BulletController 迁移并增强) */ private initializeDirection() { console.log('🎯 开始初始化子弹方向...'); // 如果启用自动瞄准,寻找最近的敌人 if (this.autoTarget) { this.findNearestEnemy(); } // 设置方向 if (this.targetEnemy && this.autoTarget) { this.setDirectionToTarget(); } else { // 随机方向或使用默认方向 const randomAngle = Math.random() * Math.PI * 2; this.direction = new Vec3(Math.cos(randomAngle), Math.sin(randomAngle), 0); console.log('🎲 使用随机方向'); } // 保存初始方向(用于boomerang) this.initialDir.set(this.direction); // 应用速度 this.applyVelocity(); } /** * 寻找最近的敌人(从 BulletController 迁移) */ private findNearestEnemy() { const enemyContainer = find('Canvas/GameLevelUI/enemyContainer'); if (!enemyContainer) { console.log('❌ 未找到敌人容器'); return; } const enemies = enemyContainer.children.filter(child => child.active && (child.name.toLowerCase().includes('enemy') || child.getComponent('EnemyInstance') !== null) ); if (enemies.length === 0) { console.log('❌ 没有找到敌人'); return; } let nearestEnemy: Node = null; let nearestDistance = Infinity; const bulletPos = this.node.worldPosition; for (const enemy of enemies) { const distance = Vec3.distance(bulletPos, enemy.worldPosition); if (distance < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } if (nearestEnemy) { this.targetEnemy = nearestEnemy; console.log(`🎯 锁定目标: ${nearestEnemy.name}`); } } /** * 设置朝向目标的方向(从 BulletController 迁移) */ private setDirectionToTarget() { if (!this.targetEnemy) return; const targetPos = this.targetEnemy.worldPosition; const currentPos = this.node.worldPosition; this.direction = targetPos.clone().subtract(currentPos).normalize(); console.log('🎯 方向已设置,朝向目标敌人'); } /** * 应用速度(从 BulletController 迁移并增强) */ private applyVelocity() { if (!this.rigidBody || !this.direction) { console.error('❌ 无法应用速度:缺少刚体或方向'); return; } // 覆盖速度值(全局控制) const weaponGlobal = WeaponBlockExample.getInstance && WeaponBlockExample.getInstance(); if (weaponGlobal) { this.speed = weaponGlobal.getCurrentBulletSpeed(); } const velocity = new Vec2( this.direction.x * this.speed, this.direction.y * this.speed ); this.rigidBody.linearVelocity = velocity; console.log('✅ 子弹速度已应用:', velocity); } /** * 碰撞处理(整合原有逻辑) */ private onHit(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { const otherNode = otherCollider.node; console.log('💥 子弹碰撞:', otherNode.name); // 获取碰撞世界坐标 let contactWorldPos: Vec3 = null; if (contact && (contact as any).getWorldManifold) { const wm = (contact as any).getWorldManifold(); if (wm && wm.points && wm.points.length > 0) { contactWorldPos = new Vec3(wm.points[0].x, wm.points[0].y, 0); } } if (!contactWorldPos) { contactWorldPos = otherNode.worldPosition.clone(); } // 检查是否击中敌人 if (this.isEnemyNode(otherNode)) { console.log('🎯 击中敌人:', otherNode.name); // 对敌人造成伤害 this.damageEnemy(otherNode); // 播放击中特效 this.spawnHitEffect(contactWorldPos); // 根据子弹行为处理 this.handleBehaviorOnHit(otherNode, contactWorldPos); } else if (otherNode.name.toLowerCase().includes('wall')) { console.log('🧱 击中墙体,销毁子弹'); this.node.destroy(); } } /** * 根据子弹行为处理击中逻辑 */ private handleBehaviorOnHit(enemyNode: Node, contactPos: Vec3) { switch (this.behavior) { case 'explosive': this.node.destroy(); break; case 'piercing': // 继续飞行,不销毁 break; case 'ricochet': if (this.ricochetLeft > 0) { this.ricochetLeft--; this.bounceDirection(); } else { this.node.destroy(); } break; case 'boomerang': // 命中后照样返程,由 boomerangTimer 处理,不销毁 break; case 'area_burn': // 在地面生成灼烧特效 this.spawnEffect(this.trailEffectPath, contactPos, true); this.scheduleOnce(() => {/* burn area ends */}, this.burnDuration); this.node.destroy(); break; case 'homing': this.node.destroy(); break; } } /** * 判断是否为敌人节点(从 BulletController 迁移) */ private isEnemyNode(node: Node): boolean { const name = node.name.toLowerCase(); return name.includes('enemy') || name.includes('敌人') || node.getComponent('EnemyInstance') !== null || node.getComponent('EnemyComponent') !== null; } /** * 对敌人造成伤害(从 BulletController 迁移) */ private damageEnemy(enemyNode: Node) { console.log('⚔️ 对敌人造成伤害:', this.damage); // 尝试调用敌人的受伤方法 const enemyInstance = enemyNode.getComponent('EnemyInstance') as any; if (enemyInstance) { if (typeof enemyInstance.takeDamage === 'function') { enemyInstance.takeDamage(this.damage); return; } if (typeof enemyInstance.health === 'number') { enemyInstance.health -= this.damage; if (enemyInstance.health <= 0) { enemyNode.destroy(); } return; } } // 备用方案:通过EnemyController const enemyController = find('Canvas/GameLevelUI/EnemyController')?.getComponent('EnemyController') as any; if (enemyController && typeof enemyController.damageEnemy === 'function') { enemyController.damageEnemy(enemyNode, this.damage); } } update(dt: number) { // 只有在初始化完成后才进行更新 if (!this.isInitialized) return; // 生命周期管理 this.updateLifetime(dt); // 检查是否飞出游戏区域 this.checkOutOfBounds(); // 子弹行为更新 this.updateBehavior(dt); } /** * 更新生命周期 */ private updateLifetime(dt: number) { if (this.behavior === 'piercing' || this.behavior === 'boomerang') { this.lifeTimer -= dt; if (this.lifeTimer <= 0) { this.node.destroy(); return; } } } /** * 检查越界(从 BulletController 迁移) */ private checkOutOfBounds() { const canvas = find('Canvas'); if (!canvas || !this.node || !this.node.isValid) return; const uiTransform = canvas.getComponent(UITransform); if (!uiTransform) return; const halfWidth = uiTransform.width / 2; const halfHeight = uiTransform.height / 2; const center = canvas.worldPosition; const left = center.x - halfWidth; const right = center.x + halfWidth; const bottom = center.y - halfHeight; const top = center.y + halfHeight; // 允许子弹稍微超出边界后再销毁,避免边缘误差 const margin = 100; const pos = this.node.worldPosition; if (pos.x < left - margin || pos.x > right + margin || pos.y < bottom - margin || pos.y > top + margin) { this.node.destroy(); } } /** * 更新子弹行为 */ private updateBehavior(dt: number) { // boomerang返回 if (this.behavior === 'boomerang') { this.boomerangTimer += dt; if (this.boomerangTimer >= this.boomerangReturnDelay) { this.direction.multiplyScalar(-1); if (this.rigidBody) { this.rigidBody.linearVelocity = new Vec2( this.direction.x * this.speed, this.direction.y * this.speed ); } this.behavior = 'piercing'; // 之后按穿透逻辑处理 } } // homing 导弹 if (this.behavior === 'homing') { if (this.homingTimer > 0) { this.homingTimer -= dt; } else { const enemyContainer = find('Canvas/GameLevelUI/enemyContainer'); if (enemyContainer && enemyContainer.children.length > 0) { let nearest: Node = null; let dist = 1e9; for (const e of enemyContainer.children) { if (!e.active) continue; const d = Vec3.distance(this.node.worldPosition, e.worldPosition); if (d < dist) { dist = d; nearest = e; } } if (nearest) { const dir = nearest.worldPosition.clone().subtract(this.node.worldPosition).normalize(); // 线性插值方向 this.direction.lerp(dir, this.homingStrength * dt); if (this.rigidBody) { this.rigidBody.linearVelocity = new Vec2( this.direction.x * this.speed, this.direction.y * this.speed ); } } } } } } /** * 添加拖尾特效 */ private attachTrail() { this.spawnEffect(this.trailEffectPath, new Vec3(), true, this.node); } /** * 生成特效(整合并增强) */ private spawnEffect(path: string, worldPos: Vec3, loop = false, parent?: Node) { if (!path) return; const match = path.match(/tx\/(tx\d{4})/); const code = match ? match[1] : ''; const skeletonPath = code ? `Animation/WeaponTx/${code}/${code}` : path; console.log('✨ 尝试加载特效:', skeletonPath); resources.load(skeletonPath, sp.SkeletonData, (err, skData: sp.SkeletonData) => { if (err) { console.warn('加载特效失败:', skeletonPath, err); return; } const effectNode = new Node('Effect'); const skeleton: sp.Skeleton = effectNode.addComponent(sp.Skeleton); skeleton.skeletonData = skData; skeleton.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; } else { effectNode.setWorldPosition(worldPos.clone()); } } } if (!loop) { skeleton.setCompleteListener(() => { effectNode.destroy(); }); } }); } /** * 生成击中特效(从 BulletController 迁移) */ private spawnHitEffect(worldPos: Vec3) { this.spawnEffect(this.hitEffectPath, worldPos, false); } /** * 弹射方向计算 */ private bounceDirection() { // 简单随机旋转 ricochetAngle const sign = Math.random() < 0.5 ? -1 : 1; const rad = this.ricochetAngle * (Math.PI / 180) * sign; const cos = Math.cos(rad), sin = Math.sin(rad); const nx = this.direction.x * cos - this.direction.y * sin; const ny = this.direction.x * sin + this.direction.y * cos; this.direction.set(nx, ny, 0); if (this.rigidBody) { this.rigidBody.linearVelocity = new Vec2( this.direction.x * this.speed, this.direction.y * this.speed ); } } }