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.
404 lines
15 KiB
404 lines
15 KiB
import { _decorator, Component, Node, Vec3, Vec2, v3, v2, CCInteger, CCFloat, Collider2D, Contact2DType, IPhysics2DContact, RigidBody2D, ERigidBody2DType, sp } from 'cc';
|
|
import { BattleDataManager, BezierFallEffectType } from '../BattleDataManager';
|
|
import { Monster } from '../Monster';
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
// 无人机状态枚举
|
|
enum DroneState {
|
|
ORBITING, // 轨道飞行
|
|
CHASING, // 追击目标
|
|
ATTACKING, // 攻击中
|
|
RETURNING // 返回轨道
|
|
}
|
|
|
|
@ccclass('Drone')
|
|
export class Drone extends Component {
|
|
private getDroneManager() {
|
|
return this.node.parent?.getComponent('DroneManager') as any;
|
|
}
|
|
@property
|
|
public moveSpeed: number = 300; // 移动速度
|
|
@property
|
|
public attackRange: number = 100; // 攻击范围
|
|
@property
|
|
public attackDamage: number = 20; // 攻击力
|
|
@property
|
|
public attackInterval: number = 1; // 攻击间隔(秒)
|
|
@property
|
|
public orbitSpeed: number = 1; // 轨道旋转速度(弧度/秒)
|
|
@property(Node)
|
|
public bulletLaunchNode: Node | null = null; // 子弹发射节点
|
|
|
|
|
|
private currentState: DroneState = DroneState.ORBITING;
|
|
private orbitParams: {center: Vec3, a: number, b: number} = {
|
|
center: new Vec3(200, 360, 0), // 椭圆中心
|
|
a: 150, // 长轴
|
|
b: 100 // 短轴
|
|
};
|
|
private orbitAngle: number = 0; // 轨道角度
|
|
private targetMonster: Node | null = null;
|
|
private attackTimer: number = 0;
|
|
private returnPoint: Vec3 = new Vec3();
|
|
private positionOffset: Vec3 = new Vec3(); // 用于避免重叠的偏移
|
|
private _tmpOrbitPos: Vec3 = new Vec3();
|
|
private _tmpTargetPos: Vec3 = new Vec3();
|
|
private _targetMonsterComp: Monster | null = null;
|
|
|
|
private _offsetRecalcTimer = 0;
|
|
private _offsetRecalcInterval = 0.12;
|
|
private fuelCount: number = 0; // 燃油计数
|
|
_sk:sp.Skeleton = null!;
|
|
curAnimationName = ''
|
|
|
|
protected onLoad(): void {
|
|
this._sk = this.node.getComponentInChildren(sp.Skeleton)
|
|
}
|
|
/**
|
|
* 播放 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(){
|
|
let nameStr = ''
|
|
if(this.currentState == DroneState.ATTACKING){
|
|
nameStr = '1无人机漂浮'
|
|
}else{
|
|
nameStr = '2无人机原地飞'
|
|
}
|
|
if(this.curAnimationName != nameStr){
|
|
this.curAnimationName = nameStr
|
|
this.playSpine(nameStr)
|
|
}
|
|
}
|
|
// 初始化无人机轨道参数
|
|
public init(orbitCenter: Vec3, a: number, b: number, startAngle: number) {
|
|
this.orbitParams.center = orbitCenter;
|
|
this.orbitParams.a = a;
|
|
this.orbitParams.b = b;
|
|
this.orbitAngle = startAngle;
|
|
|
|
// 设置初始位置
|
|
const startPos = this.calculateOrbitPosition(startAngle);
|
|
this.node.setPosition(startPos);
|
|
}
|
|
|
|
// 计算椭圆轨道上的位置
|
|
private calculateOrbitPosition(angle: number): Vec3 {
|
|
const x = this.orbitParams.center.x + this.orbitParams.a * Math.cos(angle);
|
|
const y = this.orbitParams.center.y + this.orbitParams.b * Math.sin(angle);
|
|
this._tmpOrbitPos.x = x;
|
|
this._tmpOrbitPos.y = y;
|
|
this._tmpOrbitPos.z = 0;
|
|
return this._tmpOrbitPos;
|
|
}
|
|
update(deltaTime: number) {
|
|
if (!gg.game.CurentBattle.canUpdateFrame()) return;
|
|
const scaledDt = deltaTime * gg.game.CurentBattle.TimeScale;
|
|
this.playEffect()
|
|
switch (this.currentState) {
|
|
case DroneState.ORBITING:
|
|
this.updateOrbiting(scaledDt);
|
|
break;
|
|
case DroneState.CHASING:
|
|
this.updateChasing(scaledDt);
|
|
break;
|
|
case DroneState.ATTACKING:
|
|
this.updateAttacking(scaledDt);
|
|
break;
|
|
case DroneState.RETURNING:
|
|
this.updateReturning(scaledDt);
|
|
break;
|
|
}
|
|
}
|
|
// 寻找攻击范围内的怪物
|
|
private findTarget(){
|
|
// const monsters = find('Monsters')?.getComponentsInChildren(Monster) || [];
|
|
// const dronePos = this.node.position;
|
|
|
|
// for (const monster of monsters) {
|
|
// if (!monster.node.active) continue;
|
|
|
|
// const distance = Vec3.distance(dronePos, monster.node.position);
|
|
// if (distance <= this.attackRange) {
|
|
// return monster;
|
|
// }
|
|
// }
|
|
// return null;
|
|
let monster = gg.game.CurentBattle.MonsterSpawner.getDroneTargetMonster(this.node,this.attackRange);
|
|
return monster;
|
|
}
|
|
|
|
// 检查攻击同一目标的其他无人机
|
|
private getSameTargetDrones(): Drone[] {
|
|
if (!this.targetMonster) return [];
|
|
|
|
const allDrones = this.node.parent.getComponentsInChildren(Drone) || [];
|
|
return allDrones.filter(drone =>
|
|
drone.targetMonster === this.targetMonster &&
|
|
(drone.currentState === DroneState.ATTACKING || drone.currentState === DroneState.CHASING)
|
|
);
|
|
}
|
|
|
|
// 更新位置偏移以避免重叠
|
|
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(targetPos: Vec3, deltaTime: number): boolean {
|
|
const currentPos = this.node.position;
|
|
const dx = targetPos.x - currentPos.x;
|
|
const dy = targetPos.y - currentPos.y;
|
|
const dz = targetPos.z - currentPos.z;
|
|
const distSq = dx * dx + dy * dy + dz * dz;
|
|
const moveStep = this.moveSpeed * deltaTime;
|
|
|
|
const moveStepSq = moveStep * moveStep;
|
|
if (distSq <= moveStepSq) {
|
|
this.node.setPosition(targetPos.x, targetPos.y, targetPos.z);
|
|
return true;
|
|
}
|
|
|
|
const dist = Math.sqrt(distSq);
|
|
const ratio = moveStep / dist;
|
|
this.node.setPosition(
|
|
currentPos.x + dx * ratio,
|
|
currentPos.y + dy * ratio,
|
|
currentPos.z + dz * ratio
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// 寻找最近的轨道点
|
|
private findNearestOrbitPoint(): Vec3 {
|
|
const currentPos = this.node.position;
|
|
let nearestAngle = 0;
|
|
let minDistance = Infinity;
|
|
|
|
// 采样多个角度找到最近点
|
|
for (let angle = 0; angle < Math.PI * 2; angle += 0.1) {
|
|
const pos = this.calculateOrbitPosition(angle);
|
|
const dist = Vec3.distance(currentPos, pos);
|
|
|
|
if (dist < minDistance) {
|
|
minDistance = dist;
|
|
nearestAngle = angle;
|
|
}
|
|
}
|
|
|
|
this.orbitAngle = nearestAngle;
|
|
return this.calculateOrbitPosition(nearestAngle);
|
|
}
|
|
|
|
// 轨道飞行状态更新
|
|
private updateOrbiting(deltaTime: number) {
|
|
// 检查是否有可攻击目标
|
|
const target = this.findTarget();
|
|
if (target) {
|
|
this.targetMonster = target;
|
|
this._targetMonsterComp = target.getComponent(Monster);
|
|
this.currentState = DroneState.CHASING;
|
|
this._offsetRecalcTimer = this._offsetRecalcInterval;
|
|
return;
|
|
}
|
|
|
|
// 沿椭圆轨道移动
|
|
this.orbitAngle += this.orbitSpeed * deltaTime;
|
|
if (this.orbitAngle >= Math.PI * 2) {
|
|
this.orbitAngle -= Math.PI * 2;
|
|
}
|
|
|
|
const newPos = this.calculateOrbitPosition(this.orbitAngle);
|
|
this.node.setPosition(newPos);
|
|
}
|
|
|
|
// 追击状态更新
|
|
private updateChasing(deltaTime: number) {
|
|
if (!this.targetMonster || !this.targetMonster.isValid) {
|
|
this._targetMonsterComp = null;
|
|
this.currentState = DroneState.RETURNING;
|
|
this.returnPoint = this.findNearestOrbitPoint();
|
|
return;
|
|
}
|
|
|
|
if (!this._targetMonsterComp) this._targetMonsterComp = this.targetMonster.getComponent(Monster);
|
|
if (this._targetMonsterComp?.getIsDead()) {
|
|
this._targetMonsterComp = null;
|
|
this.currentState = DroneState.RETURNING;
|
|
this.returnPoint = this.findNearestOrbitPoint();
|
|
return;
|
|
}
|
|
|
|
// 计算目标上方50单位的位置并应用偏移
|
|
this._offsetRecalcTimer += deltaTime;
|
|
if (this._offsetRecalcTimer >= this._offsetRecalcInterval) {
|
|
this._offsetRecalcTimer = 0;
|
|
this.updatePositionOffset();
|
|
}
|
|
|
|
const tp = this.targetMonster.position;
|
|
this._tmpTargetPos.x = tp.x + this.positionOffset.x;
|
|
this._tmpTargetPos.y = tp.y + 200 + this.positionOffset.y;
|
|
this._tmpTargetPos.z = tp.z + this.positionOffset.z;
|
|
|
|
// 移动到目标位置
|
|
const reached = this.moveToTarget(this._tmpTargetPos, deltaTime);
|
|
if (reached) {
|
|
this.currentState = DroneState.ATTACKING;
|
|
this._offsetRecalcTimer = this._offsetRecalcInterval;
|
|
this.attackTimer = 0;
|
|
}
|
|
}
|
|
|
|
// 攻击状态更新
|
|
private updateAttacking(deltaTime: number) {
|
|
if (!this.targetMonster || !this.targetMonster.isValid) {
|
|
this._targetMonsterComp = null;
|
|
// 目标已消失,检查新目标
|
|
const newTarget = this.findTarget();
|
|
if (newTarget) {
|
|
this.targetMonster = newTarget;
|
|
this._targetMonsterComp = newTarget.getComponent(Monster);
|
|
this.currentState = DroneState.CHASING;
|
|
this._offsetRecalcTimer = this._offsetRecalcInterval;
|
|
} else {
|
|
this.currentState = DroneState.RETURNING;
|
|
this._targetMonsterComp = null;
|
|
this.returnPoint = this.findNearestOrbitPoint();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!this._targetMonsterComp) this._targetMonsterComp = this.targetMonster.getComponent(Monster);
|
|
if (this._targetMonsterComp?.getIsDead()) {
|
|
const newTarget = this.findTarget();
|
|
if (newTarget) {
|
|
this.targetMonster = newTarget;
|
|
this._targetMonsterComp = newTarget.getComponent(Monster);
|
|
this.currentState = DroneState.CHASING;
|
|
this._offsetRecalcTimer = this._offsetRecalcInterval;
|
|
} else {
|
|
this.currentState = DroneState.RETURNING;
|
|
this._targetMonsterComp = null;
|
|
this.returnPoint = this.findNearestOrbitPoint();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 保持在目标上方
|
|
this._offsetRecalcTimer += deltaTime;
|
|
if (this._offsetRecalcTimer >= this._offsetRecalcInterval) {
|
|
this._offsetRecalcTimer = 0;
|
|
this.updatePositionOffset();
|
|
}
|
|
|
|
const tp = this.targetMonster.position;
|
|
this.node.setPosition(
|
|
tp.x + this.positionOffset.x,
|
|
tp.y + 200 + this.positionOffset.y,
|
|
tp.z + this.positionOffset.z
|
|
);
|
|
|
|
// 攻击计时器
|
|
// this.attackTimer += deltaTime;
|
|
// if (this.attackTimer >= this.attackInterval) {
|
|
// this.attackTimer = 0;
|
|
// this.attackTarget();
|
|
// }
|
|
}
|
|
|
|
// 返回轨道状态更新
|
|
private updateReturning(deltaTime: number) {
|
|
// 检查是否有新目标
|
|
const newTarget = this.findTarget();
|
|
if (newTarget) {
|
|
this.targetMonster = newTarget;
|
|
this._targetMonsterComp = newTarget.getComponent(Monster);
|
|
this.currentState = DroneState.CHASING;
|
|
this._offsetRecalcTimer = this._offsetRecalcInterval;
|
|
return;
|
|
}
|
|
|
|
// 返回轨道
|
|
const reached = this.moveToTarget(this.returnPoint, deltaTime);
|
|
if (reached) {
|
|
this.currentState = DroneState.ORBITING;
|
|
this.targetMonster = null;
|
|
this._targetMonsterComp = null;
|
|
}
|
|
}
|
|
|
|
// 攻击目标
|
|
public attackTarget(weaponId:number) {
|
|
if (this.currentState == DroneState.ATTACKING && this.targetMonster && this.targetMonster.isValid && !this.targetMonster.getComponent(Monster).getIsDead()) {
|
|
// console.log("攻击目标attackTarget");
|
|
let now = new Date().getTime()
|
|
const manager = this.getDroneManager();
|
|
if(manager && now - manager.audio_timeStamp > 140){
|
|
manager.audio_timeStamp = now
|
|
gg.audio.playEffect({ name: '无人机攻击',path:'sound/武器音效/'});
|
|
}
|
|
|
|
let isFuelBomb = false
|
|
//特殊技能(丢燃烧弹)
|
|
let weaponPropertyData = BattleDataManager.getWeaponAddProperty(weaponId)
|
|
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;
|
|
gg.game.CurentBattle.SkillController.executeDroneWeaponSkill(weaponId,this.bulletLaunchNode.worldPosition.clone().add(v3(0,-20,0)),this.targetMonster.position.clone(),true)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if(!isFuelBomb){
|
|
gg.game.CurentBattle.SkillController.executeDroneWeaponSkill(weaponId,this.bulletLaunchNode.worldPosition.clone().add(v3(0,-20,0)),this.targetMonster.position.clone(),false)
|
|
}
|
|
}
|
|
}
|
|
} |