import { _decorator, Component, Node, Vec3, Vec2, v3, v2, CCInteger, CCFloat, Collider2D, Contact2DType, IPhysics2DContact, RigidBody2D, ERigidBody2DType, sp, instantiate, isValid, tween } from 'cc'; import { BattleDataManager, BezierFallEffectType, IWeaponProperty } from '../BattleDataManager'; import { Skill } from './Skill'; import { Monster } from '../Monster'; const { ccclass, property } = _decorator; // 无人机状态枚举 enum DroneState { MOVE,// 移动 ATTACKING, // 攻击中 } @ccclass('DroneBullet') export class DroneBullet extends Component { @property public moveSpeed: number = 100; // 移动速度 @property public attackRange: number = 200; // 攻击范围 currentState: DroneState = DroneState.MOVE; targetMonster: Node = null!; private positionOffset: Vec3 = new Vec3(); // 用于避免重叠的偏移 private fuelCount: number = 0; // 燃油计数 _sk: sp.Skeleton = null!; curAnimationName = '' _dt = -1; _attackTime = 10000; /**存在时间 */ private existTime: number = 0; weaponProperty: IWeaponProperty = null!; private _targetMonsterComp: Monster | null = null; private _offsetRecalcTimer = 0; private _offsetRecalcInterval = 0.12; protected onLoad(): void { this._sk = this.node.getComponentInChildren(sp.Skeleton) } setData(weaponProperty: IWeaponProperty, target: Node) { this.weaponProperty = weaponProperty; this.targetMonster = target; this._targetMonsterComp = this.targetMonster?.getComponent(Monster) || null; this.currentState = DroneState.MOVE; this._attackTime = weaponProperty.damageInterval; this.existTime = weaponProperty.weaponExistTime; this.playEffect("2无人机原地飞"); } /** * 播放 Spine 动画 * @param name 动画名称 * @param callback 动画播放完成后的回调函数 * @param timeScale 动画播放速度倍率 * @param loop 是否循环播放 */ private playSpine(name: string, callback: Function = null, timeScale = 1, loop = true) { // 如果节点不存在,则直接返回 if (this.node == null) return; // 设置动画播放完成后的回调函数 // this._sk.setCompleteListener(() => { // if (callback) callback(); // }); // 设置动画播放速度倍率,考虑了战斗速度的影响 // this._sk.timeScale = timeScale * 1//BattleManager.Instance.LevelBattle.BattleSpeed; // 播放指定名称的动画,并设置是否循环播放 this._sk.setAnimation(0, name, loop); } playEffect(name) { let nameStr = name //nameStr = '1无人机漂浮' // if (this.currentState == DroneState.ATTACKING) { // } else { // nameStr = '2无人机原地飞' // } if (this.curAnimationName != nameStr) { this.curAnimationName = nameStr this.playSpine(nameStr) } } update(deltaTime: number) { if (!gg.game.CurentBattle.canUpdateFrame()) return; const scaledDt = deltaTime * gg.game.CurentBattle.TimeScale; this.existTime -= scaledDt; if (this.existTime <= 0) { this.remove(); return; } switch (this.currentState) { case DroneState.MOVE: if (!this.targetMonster || !isValid(this.targetMonster)) { const newTarget = this.findTarget(); if (newTarget) { this.targetMonster = newTarget; this._targetMonsterComp = newTarget.getComponent(Monster); } else { // 没找到目标时保持待机,直到存在时间结束 this.playEffect("2无人机原地飞"); break; } } if (this.moveToTarget(scaledDt)) { this.currentState = DroneState.ATTACKING; this.updatePositionOffset(); // 攻击开始时先拉一次偏移 this._offsetRecalcTimer = 0; this.playEffect("1无人机漂浮"); } break; case DroneState.ATTACKING: this.updateAttacking(scaledDt); // updateAttacking 里可能因目标死亡/丢失切回 MOVE;该帧不应继续扣计时并提前开火 if (this.currentState !== DroneState.ATTACKING) { break; } this._attackTime -= scaledDt; if (this._attackTime <= 0) { this.attackTarget(); } break; } } // 寻找攻击范围内的怪物 private findTarget() { let monster = gg.game.CurentBattle.MonsterSpawner.getDroneTargetMonster(this.node, this.attackRange); return monster; } // 检查攻击同一目标的其他无人机 private getSameTargetDrones(): DroneBullet[] { if (!this.targetMonster) return []; const allDrones = this.node.parent.getComponentsInChildren(DroneBullet) || []; return allDrones.filter(drone => drone.targetMonster === this.targetMonster && (drone.currentState === DroneState.ATTACKING) ); } // 更新位置偏移以避免重叠 private updatePositionOffset() { const sameTargetDrones = this.getSameTargetDrones(); const totalCount = sameTargetDrones.length + 1; // 包括自己 if (totalCount <= 1) { this.positionOffset.set(0, 0, 0); return; } // 计算偏移角度,形成圆形分布 const index = sameTargetDrones.indexOf(this); // const angle = (index + 1) * (Math.PI * 2 / totalCount); const offsetDistance = 50; // 偏移距离,小于无人机一半大小(25) let x = -(totalCount - 1) * offsetDistance / 2 + index * offsetDistance; this.positionOffset.set(x, 0, 0); // this.positionOffset.set( // Math.cos(angle) * offsetDistance, // Math.sin(angle) * offsetDistance, // 0 // ); } // 移动到目标位置 private moveToTarget(deltaTime: number): boolean { if (!this.targetMonster || !isValid(this.targetMonster)) return false; const tp = this.targetMonster.position; const tx = tp.x + 50 + this.positionOffset.x; const ty = tp.y + 180 + this.positionOffset.y; const tz = tp.z + this.positionOffset.z; const cp = this.node.position; const cx = cp.x; const cy = cp.y; const cz = cp.z; const dx = tx - cx; const dy = ty - cy; const dz = tz - cz; const distSq = dx * dx + dy * dy + dz * dz; const moveStep = this.moveSpeed * deltaTime; const moveStepSq = moveStep * moveStep; if (distSq <= moveStepSq) { this.node.setPosition(tx, ty, tz); return true; } const dist = Math.sqrt(distSq); const ratio = moveStep / dist; this.node.setPosition(cx + dx * ratio, cy + dy * ratio, cz + dz * ratio); return false; } // 攻击状态更新 private updateAttacking(deltaTime: number) { if (!this.targetMonster || !this.targetMonster.isValid) { // 目标已消失,检查新目标 const newTarget = this.findTarget(); if (newTarget) { this.targetMonster = newTarget; this._targetMonsterComp = newTarget.getComponent(Monster); this.currentState = DroneState.MOVE; this._attackTime = this.weaponProperty?.damageInterval ?? this._attackTime; } else { // 不立即消失,回到移动态继续等待/搜敌 this.targetMonster = null; this._targetMonsterComp = null; this.currentState = DroneState.MOVE; this.playEffect("2无人机原地飞"); } return; } if (!this._targetMonsterComp) this._targetMonsterComp = this.targetMonster.getComponent(Monster); if (this._targetMonsterComp?.IsDead) { const newTarget = this.findTarget(); if (newTarget) { this.targetMonster = newTarget; this._targetMonsterComp = newTarget.getComponent(Monster); this.currentState = DroneState.MOVE; this._attackTime = this.weaponProperty?.damageInterval ?? this._attackTime; } else { this.targetMonster = null; this._targetMonsterComp = null; this.currentState = DroneState.MOVE; this.playEffect("2无人机原地飞"); } return; } // 保持在目标上方 this._offsetRecalcTimer += deltaTime; if (this._offsetRecalcTimer >= this._offsetRecalcInterval) { this._offsetRecalcTimer = 0; this.updatePositionOffset(); } const tp = this.targetMonster.position; this.node.setPosition( tp.x + 50 + this.positionOffset.x, tp.y + 180 + this.positionOffset.y, tp.z + this.positionOffset.z ); } // 攻击目标 public attackTarget() { if (this.targetMonster && this.targetMonster.isValid) { if (!this._targetMonsterComp) this._targetMonsterComp = this.targetMonster.getComponent(Monster); if (!this._targetMonsterComp || this._targetMonsterComp.IsDead) return; // console.log("攻击目标attackTarget"); //if (Vec3.distance(this.node, this.targetMonster) > this.attackRange) return; this._attackTime = this.weaponProperty.damageInterval; // 节流交给 SoundManager(按音效名);多无人机勿抢同一本地锁挡掉射击声 gg.audio.playEffect({ name: '无人机攻击', path: 'sound/武器音效/' }); let isFuelBomb = false //特殊技能(丢燃烧弹) let weaponPropertyData = this.node.getComponent(Skill).weaponPropertyData; let specialSkillAddFallEffectInfo = weaponPropertyData.specialSkillAddFallEffectInfo for (let i = 0; i < specialSkillAddFallEffectInfo.length; i++) { let buffInfo = specialSkillAddFallEffectInfo[i] let arr = buffInfo.split(',') let buffType = Number(arr[0]) //落地添加燃烧区域 if (buffType == BezierFallEffectType.fallBombBurnArea) { let atkCount = Number(arr[7])//多少次普攻发射一次燃油弹 this.fuelCount++ if (this.fuelCount >= atkCount) { isFuelBomb = true this.fuelCount = 0; this.executeDroneWeaponSkill(true) break } } } if (!isFuelBomb) { this.executeDroneWeaponSkill(false) } } } /** * 执行武器技能逻辑(无人机) * @param weaponId 武器ID * @param startPos 起始位置 * @param isFuel 是否是燃油弹 */ executeDroneWeaponSkill(isFuel: boolean) { const weaponState = this.getComponent(Skill).weaponData; const startPos = this.node.find("bulletLaunchNode").worldPosition.clone().add(new Vec3(0, -50, 0)); const targetPos = this.targetMonster.position.clone(); if (!weaponState) return; let bulletNode = this.node.find("bullet") let weaponNode = instantiate(bulletNode); weaponNode.setParent(gg.game.CurentBattle.SkillController.node); weaponNode.active = true; weaponNode.worldPosition = startPos let skillComp = weaponNode.getComponent(Skill); skillComp.setData(weaponState, targetPos, false, isFuel); } remove() { this.currentState = DroneState.MOVE; this.existTime = 100000; this._attackTime = 100000; this.targetMonster = null; tween(this.node).to(0.5, { opacity: 0 }) .call(() => { gg.res.putNode(this.node); }) .start() } }