You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
12 KiB
324 lines
12 KiB
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()
|
|
}
|
|
} |