import { _decorator, Component, Node, Vec3, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, Sprite, find, SpriteFrame } from 'cc'; import { EnemyAttackEffectManager, AttackEffectType } from '../EnemyAttackAni/EnemyAttackEffectManager'; import { BundleLoader } from '../../Core/BundleLoader'; import { Audio } from '../../AudioManager/AudioManager'; import EventBus, { GameEvents } from '../../Core/EventBus'; const { ccclass, property } = _decorator; /** * 敌人抛掷物实例组件 * 负责处理单个抛掷物的移动、碰撞和伤害 * 此组件应该挂载到抛掷物预制体上 */ @ccclass('EnemyProjectileInstance') export class EnemyProjectileInstance extends Component { // 抛掷物属性 private damage: number = 10; private speed: number = 100; private direction: Vec3 = new Vec3(0, -1, 0); // 默认向下 private projectileType: string = 'arrow'; // 组件引用 private rigidBody: RigidBody2D = null; private collider: Collider2D = null; private sprite: Sprite = null; private imageNode: Node = null; // 生命周期 private maxLifetime: number = 5.0; // 最大存活时间 private currentLifetime: number = 0; // 弧线弹道相关属性 private useArcTrajectory: boolean = true; // 是否使用弧线弹道 private arcDir: Vec3 = null; // 当前方向 private arcTargetDir: Vec3 = null; // 目标方向 private targetWallPosition: Vec3 = null; // 目标墙体位置 private rotateSpeed: number = 2.0; // 转向速度 onLoad() { console.log('[EnemyProjectileInstance] 抛掷物实例组件已加载'); this.setupComponents(); this.setupProjectileSprite(); } start() { this.setupCollisionListener(); } update(deltaTime: number) { // 如果已被标记为销毁,停止更新 if (this.isDestroyed) { return; } // 更新生命周期 this.currentLifetime += deltaTime; if (this.currentLifetime >= this.maxLifetime) { console.log('[EnemyProjectileInstance] 抛掷物超时,自动销毁'); this.isDestroyed = true; this.destroyProjectile(); return; } // 根据弹道类型移动抛掷物 if (this.useArcTrajectory) { this.updateArcTrajectory(deltaTime); } else { this.moveProjectile(deltaTime); } } /** * 初始化抛掷物 * @param config 抛掷物配置 */ public init(config: { damage: number; speed: number; direction: Vec3; projectileType: string; startPosition: Vec3; }) { console.log('[EnemyProjectileInstance] 初始化抛掷物配置:', config); this.damage = config.damage; this.speed = config.speed; this.direction = config.direction.clone().normalize(); this.projectileType = config.projectileType; // 注意:不在这里设置位置,因为节点还没有添加到Canvas // 位置设置由EnemyProjectile.ts在添加到Canvas后处理 // 初始化弧线弹道 this.initArcTrajectory(); // 根据类型刷新外观(异步加载) this.setupProjectileSprite(); console.log(`[EnemyProjectileInstance] 初始化抛掷物完成: 类型=${this.projectileType}, 伤害=${this.damage}, 速度=${this.speed}`); console.log('[EnemyProjectileInstance] 起始位置:', config.startPosition); } /** * 获取预制体中已配置的组件 */ private setupComponents() { // 获取预制体中已配置的刚体组件 this.rigidBody = this.getComponent(RigidBody2D); if (!this.rigidBody) { console.error('[EnemyProjectileInstance] 预制体中未找到RigidBody2D组件'); } else { console.log('[EnemyProjectileInstance] 找到RigidBody2D组件'); } // 获取预制体中已配置的碰撞器组件 this.collider = this.getComponent(Collider2D); if (!this.collider) { console.error('[EnemyProjectileInstance] 预制体中未找到Collider2D组件'); } else { console.log('[EnemyProjectileInstance] 找到Collider2D组件'); } // 获取Image子节点上的精灵组件(新结构) this.imageNode = this.node.getChildByName('Image') || null; if (!this.imageNode) { console.warn('[EnemyProjectileInstance] 未找到子节点 Image,尝试在子层级搜索Sprite组件'); } this.sprite = this.imageNode ? this.imageNode.getComponent(Sprite) : this.getComponentInChildren(Sprite); if (!this.sprite) { console.error('[EnemyProjectileInstance] 未找到Sprite组件,请确保在 EnemyProjectile/Image 节点上挂载 Sprite'); } else { console.log('[EnemyProjectileInstance] 找到Sprite组件(节点:' + (this.sprite.node?.name || '未知') + ')'); } } /** * 设置抛掷物外观: * - 法师僵尸 (magic_bolt) 使用 Animation/EnemyAni/003/ball.png * - 其它类型保持预制体默认图片 */ private async setupProjectileSprite() { if (!this.sprite) return; try { if (this.projectileType === 'magic_bolt') { const loader = BundleLoader.getInstance(); const framePath = 'EnemyAni/003/ball/spriteFrame'; const spriteFrame = await loader.loadAssetFromBundle('Animation', framePath, SpriteFrame); if (spriteFrame && this.sprite && this.sprite.isValid) { this.sprite.spriteFrame = spriteFrame; console.log('[EnemyProjectileInstance] 已应用法师抛掷物外观: ball.png'); } } else { // 保持预制体默认图片(例如弓箭手箭矢),不做修改 console.log(`[EnemyProjectileInstance] 使用默认抛掷物外观,类型=${this.projectileType}`); } } catch (err) { console.error('[EnemyProjectileInstance] 加载抛掷物SpriteFrame失败', err); } } /** * 设置碰撞监听 */ private setupCollisionListener() { if (this.collider) { this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); console.log('[EnemyProjectileInstance] 碰撞监听设置完成(BEGIN_CONTACT绑定)'); } } /** * 移动抛掷物 */ private moveProjectile(deltaTime: number) { if (!this.rigidBody) return; // 计算移动向量 const moveVector = this.direction.clone().multiplyScalar(this.speed * deltaTime); // 获取当前位置并添加移动向量 const currentPos = this.node.getWorldPosition(); const newPos = currentPos.add(moveVector); // 设置新位置 this.node.setWorldPosition(newPos); } /** * 初始化弧线弹道 */ private initArcTrajectory() { if (!this.useArcTrajectory) return; // 寻找最近的墙体作为目标 this.findNearestWall(); // 计算带45°随机偏移的初始方向 const baseDir = this.direction.clone().normalize(); const sign = Math.random() < 0.5 ? 1 : -1; const rad = 45 * Math.PI / 180 * sign; const cos = Math.cos(rad); const sin = Math.sin(rad); const offsetDir = new Vec3( baseDir.x * cos - baseDir.y * sin, baseDir.x * sin + baseDir.y * cos, 0 ).normalize(); this.arcDir = offsetDir; // 如果找到了目标墙体,设置目标方向 if (this.targetWallPosition) { const currentPos = this.node.getWorldPosition(); const dirToWall = this.targetWallPosition.clone().subtract(currentPos).normalize(); this.arcTargetDir = dirToWall; } else { // 如果没有找到墙体,使用基础方向 this.arcTargetDir = baseDir; } console.log(`[EnemyProjectileInstance] 初始化弧线弹道: 目标墙体位置=${this.targetWallPosition}`); } /** * 寻找最近的墙体 */ private findNearestWall() { const currentPos = this.node.getWorldPosition(); let nearestWall: Node = null; let nearestDistance = Infinity; // 方法1:通过EnemyController获取墙体节点 const enemyController = find('Canvas/GameLevelUI/EnemyController')?.getComponent('EnemyController') as any; if (enemyController) { const wallNodes = []; if (enemyController.topFenceNode) wallNodes.push(enemyController.topFenceNode); if (enemyController.bottomFenceNode) wallNodes.push(enemyController.bottomFenceNode); for (const wall of wallNodes) { if (wall && wall.active) { const distance = Vec3.distance(currentPos, wall.getWorldPosition()); if (distance < nearestDistance) { nearestDistance = distance; nearestWall = wall; } } } } // 方法2:在GameArea中搜索墙体节点 if (!nearestWall) { const gameArea = find('Canvas/GameLevelUI/GameArea'); if (gameArea) { for (let i = 0; i < gameArea.children.length; i++) { const child = gameArea.children[i]; if (this.isWallNode(child) && child.active) { const distance = Vec3.distance(currentPos, child.getWorldPosition()); if (distance < nearestDistance) { nearestDistance = distance; nearestWall = child; } } } } } // 方法3:直接搜索常见墙体路径 if (!nearestWall) { const wallPaths = [ 'Canvas/GameLevelUI/GameArea/TopFence', 'Canvas/GameLevelUI/GameArea/BottomFence', 'Canvas/GameLevelUI/Wall', 'Canvas/Wall', 'Canvas/TopWall', 'Canvas/BottomWall' ]; for (const path of wallPaths) { const wall = find(path); if (wall && wall.active) { const distance = Vec3.distance(currentPos, wall.getWorldPosition()); if (distance < nearestDistance) { nearestDistance = distance; nearestWall = wall; } } } } // 设置目标墙体位置 if (nearestWall) { this.targetWallPosition = nearestWall.getWorldPosition().clone(); } } private updateArcTrajectory(deltaTime: number) { if (!this.arcDir || !this.arcTargetDir) { this.moveProjectile(deltaTime); return; } // 平滑转向到目标方向 const currentDir = this.arcDir.clone(); const targetDir = this.arcTargetDir.clone(); // 插值计算新方向 const lerpFactor = Math.min(1, this.rotateSpeed * deltaTime); const newDir = new Vec3( currentDir.x + (targetDir.x - currentDir.x) * lerpFactor, currentDir.y + (targetDir.y - currentDir.y) * lerpFactor, 0 ).normalize(); this.arcDir = newDir; this.direction = newDir; // 以当前方向移动 this.moveProjectile(deltaTime); } private isDestroyed: boolean = false; private onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { if (this.isDestroyed) return; const otherNode = otherCollider.node; const otherPath = this.getNodePath(otherNode); console.log(`[EnemyProjectileInstance] BEGIN_CONTACT -> 节点: ${otherNode.name} (${otherPath}), 有接触信息: ${!!contact}`); if (this.isWallNode(otherNode)) { // 撞到墙体 this.hitWall(contact); } else if (this.isPlayerBlock(otherNode)) { // 撞到玩家方块 // TODO: 造成伤害(如果有需要) this.playHitSound(); this.destroyProjectile(); } } private getNodePath(node: Node): string { const names: string[] = []; let current: Node | null = node; while (current) { names.unshift(current.name); current = current.parent; } return names.join('/'); } private isWallNode(node: Node): boolean { const name = node.name.toLowerCase(); return name.includes('fence') || name.includes('wall'); } private isPlayerBlock(node: Node): boolean { const path = this.getNodePath(node); return path.includes('WeaponBlock') || path.includes('Block'); } private hitWall(contact?: IPhysics2DContact | null) { // 计算命中位置(优先采用物理接触点) const impactPos = this.getImpactWorldPosition(contact); this.spawnWallImpactEffect(impactPos); this.playHitSound(); this.destroyProjectile(); EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_WALL, { damage: this.damage, source: this.node }); } private getImpactWorldPosition(contact?: IPhysics2DContact | null): Vec3 { try { const c: any = contact; if (c && typeof c.getWorldManifold === 'function') { const manifold = c.getWorldManifold(); if (manifold && manifold.points && manifold.points.length > 0) { const p = manifold.points[0]; const pos = new Vec3(p.x, p.y, 0); console.log(`[EnemyProjectileInstance] 使用接触点作为命中位置: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)})`); return pos; } } } catch (e) { // 忽略异常,使用抛掷物当前位置 console.warn('[EnemyProjectileInstance] 接触点解析异常,回退为抛掷物当前位置'); } const backup = this.node.getWorldPosition().clone(); console.log(`[EnemyProjectileInstance] 使用抛掷物当前位置作为命中位置: (${backup.x.toFixed(1)}, ${backup.y.toFixed(1)})`); return backup; } private spawnWallImpactEffect(worldPos: Vec3) { const mgr = EnemyAttackEffectManager.instance; const type = this.getImpactEffectType(); if (!mgr || !type) return; console.log(`[EnemyProjectileInstance] 触发墙体命中特效: type=${type}, 位置=(${worldPos.x.toFixed(1)}, ${worldPos.y.toFixed(1)})`); mgr.playImpactEffect(type, worldPos); } private getImpactEffectType(): AttackEffectType | null { const t = (this.projectileType || '').toLowerCase(); console.log(`[EnemyProjectileInstance] 解析特效类型,projectileType=${this.projectileType}`); if (t === 'magic_bolt' || t.includes('magic')) { return AttackEffectType.Mage; } if (t === 'arrow' || t.includes('arrow')) { return AttackEffectType.Archer; } return null; } private playHitSound() { console.log('[EnemyProjectileInstance] 播放命中音效: data/弹球音效/MagicianAttack.mp3'); Audio.playEnemySound('data/弹球音效/MagicianAttack.mp3'); } private destroyProjectile() { this.isDestroyed = true; if (this.node && this.node.isValid) { console.log('[EnemyProjectileInstance] 抛掷物节点销毁'); this.node.destroy(); } } onDestroy() { // 清理逻辑,如有需要 } }