import { _decorator, BoxCollider2D, clamp01, Collider2D, Color, Component, Contact2DType, easing, IPhysics2DContact, isValid, Node, RigidBody2D, sp, Sprite, tween, Tween, UITransform, v3, Vec3 } from 'cc'; import MTools from 'db://assets/mx/tools/MTools'; import { BattlePerformance } from './BattlePerformance'; import { GBundle, UIID } from '../../game/ConfigRes'; import BombSpine from './WeaponSkill/BombSpine'; import { MonsterType } from '../../manager/MonsterDataManager'; import { MonsterBlood } from './MonsterBlood'; import { MapAgent } from './MapAgent'; import { SkillType } from './WeaponSkill/SkillTypes'; import { WeaponType } from '../../manager/WeaponDataManager'; import { BattleDataManager, BezierFallEffectType, SpecialSkillAddBuffType, SpecialSkillType } from './BattleDataManager'; import { SnowStormContinueSchedule } from './WeaponSkill/SnowStormContinueSchedule'; import { BubbleSchedule } from './WeaponSkill/BubbleSchedule'; import { acquireWeChatIosHurtFloatSpawn, HurtScore } from './HurtScore'; import { BuffControlSchedule } from './WeaponSkill/BuffControlSchedule'; import { BuffBurnSchedule } from './WeaponSkill/BuffBurnSchedule'; import { BuffIceSchedule } from './WeaponSkill/BuffIceSchedule'; import { BuffAccelerateSchedule } from './WeaponSkill/BuffAccelerateSchedule'; import { BuffElecSchedule } from './WeaponSkill/BuffElecSchedule'; import { BattleType } from '../../manager/ChapterDataManager'; import { DiamondTreasure } from './DiamondTreasure'; import { UILayer } from 'db://assets/mx/module/ui/UIManager'; import { GEvent } from 'db://assets/mx/module/event/GEvent'; import { CoinFly } from './CoinFly'; import { SingleWitch } from './NewMechanism/SingleWitch'; import { MonsterState } from './MonsterState'; import { Skill } from './WeaponSkill/Skill'; import { ConstNumType } from '../../game/ConfigProjectData'; import { SkillCostType } from '../../game/ConfigTableData'; import { Boss } from './Boss'; const { ccclass, property } = _decorator; export { MonsterState } from './MonsterState'; /**怪物标签 */ export enum MonsterTag { /**是否被控制 */ IS_BE_CONTROL = "IS_BE_CONTROL", /**是否正在被激光武器攻击 */ IS_BE_ATTACKED_BY_LASER = "IS_BE_ATTACKED_BY_LASER", } @ccclass('Monster') export class Monster extends Component { /**下击特效父节点 */ downEff: Node = null; /**上击特效父节点 */ upEff: Node = null; /**怪物皮肤父节点 */ skin: Node = null; /**血条父节点 */ bloodPos: Node = null; /**怪物主动画组件 */ anim: sp.Skeleton = null; /**怪物血条组件 */ boold: MonsterBlood = null; /**宝箱特效 */ boxEff: Node = null; /**box怪物引导特效 */ boxGuideEff: Node = null; /**移动代理 */ private _mapAgent: MapAgent = null; /**移动速度 */ private _speed: number = 0; /**特殊模式的速度倍数 */ private _specialModelSpeedMultiple: number = 1; /**速度倍数(减速buff影响) */ private _reduceSpeedMultiple: number = 1; /**速度倍数(加速buff影响) */ private _accelerateSpeedMultiple: number = 0; /**红月加速倍数 */ private _redMoonSpeedMultiple: number = 0; /**当前状态 */ public state: MonsterState = MonsterState.MOVING_FORWARD; /**上次攻击时间 */ private _lastAttackTime: number = 0; /**攻击目标 */ private _atkTarget: Node = null; /**出生点(世界坐标) */ private _bornPos: Vec3 = null; /**设置障碍物前的位置 */ private _setObstacleBeforePos: Vec3 = null; /**伤害倍数(增幅buff影响) */ private _damageMultiple: number = 1; /**冰棒武器对怪物的最后攻击时间记录,键是武器实例ID,值是最后攻击时间*/ private _iceWeaponLastAttackTime = new Map(); /**当前怪物身上的所有buff */ private _allBuff: SpecialSkillAddBuffType[] = []; /**被同种武器攻击次数 */ private _sameWeaponAtkCount: Map = new Map(); /**被同种武器攻击的累积倍数 */ private _sameWeaponAtkMultiple: number = 1; /**特殊伤害倍数(老婆飞饼每0.5s伤害增幅) */ private _damageSpecialMultiple: number = 1; private _canHideGuideEff: boolean = false; /**怪物标签状态 */ private _tagStatus: Map = new Map(); /**是不是保护期间, 保护期间怪物不会受到伤害*/ private _isProtecting: boolean = true; isMonsterPlayGude = false checkTag(tag: MonsterTag | string): boolean { return this._tagStatus.get(tag) || false; } setTag(tag: MonsterTag | string, value: boolean) { this._tagStatus.set(tag, value); } playGuide() { this.isMonsterPlayGude = true } /** 怪物上层特效:对象池优先,无则异步加载 */ private async loadUpEffPrefab(path: string, name: string, bundle: string = GBundle.BattleGameMonster): Promise { const node = await gg.res.getNodeAsync(path, name, bundle); if (!node?.isValid || !this.upEff?.isValid) return null; node.parent = this.upEff; return node; } protected onLoad(): void { this.downEff = this.node.find("downEff"); this.upEff = this.node.find("upEff"); this.skin = this.node.find("skin"); this.bloodPos = this.node.find("bloodPos"); this._mapAgent = this.node.getOrAddComponent(MapAgent); this._mapAgent.curMap = gg.game.CurentBattle.curMap; this._mapAgent.CanMove = false; } protected onEnable(): void { let collider = this.getComponent(Collider2D); if (collider) { collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); } if (gg.game.CurentBattle.ChapterId == 1) { GEvent.Ins.on(GEvent.onMovePlayer, this.onMovePlayer, this); } //GEvent.Ins.on(GEvent.BattleWarnTimeStart, this._onWarnTimeStart, this) } protected onDisable(): void { let collider = this.getComponent(Collider2D); if (collider) { collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); } if (gg.game.CurentBattle.ChapterId == 1) { GEvent.Ins.off(GEvent.onMovePlayer, this.onMovePlayer, this); } //GEvent.Ins.off(GEvent.BattleWarnTimeStart, this._onWarnTimeStart, this); } // _onWarnTimeStart(){ // console.log('this.hp', this.hp) // } protected onDestroy(): void { } Data: ITableMonster = null; /**怪物当前血量 */ hp: number = 0; /**怪物最大血量 */ maxHp: number = 0; /**护盾值 */ shield: number = 0; /**最大护盾值 */ maxShield: number = 0; setData(data: ITableMonster, hp: number) { this.Data = data; this._speed = this.Data.speed; this.IsDead = false; this._canHideGuideEff = false; this.hp = hp * gg.game.monsterHpPercent; this.maxHp = hp * gg.game.monsterHpPercent; this.shield = 0; this.maxShield = 0; this._isProtecting = true let time = gg.data.table.getConst(ConstNumType.MonsterRefreshUnselectableTime); this.scheduleOnce(() => { this._isProtecting = false; }, time); /**保护期间,时间内变化*/ this.init(); } init() { this.state = MonsterState.PATHFINDING; if (this.Data.type == MonsterType.BigBoss) { this.node.worldPositionX -= 40; this.node.setPosition(this.node.x, -350, 0); } this._bornPos = this.node.worldPosition.clone(); this._isShowWenHaoEff = false; // 兼容不同怪物 prefab 结构:优先 skin/anim,其次 skin 下任意 Spine 组件 let animNode = this.skin.find("anim"); this.anim = animNode?.getComponent(sp.Skeleton) || this.skin.getComponentInChildren(sp.Skeleton); this.syncBloodPosToSkin(); //初始化血条 if (!this.boold && this.Data && this.Data.type != MonsterType.TreasureBox) { let name = ""; if (this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss) { name = "BOSS血条底框" void gg.res.getNodeAsync('prefab/blood/', name, GBundle.BattleGameMonster).then((booldNode) => { if (!booldNode?.isValid || !this.bloodPos?.isValid) return; booldNode.setParent(this.bloodPos); booldNode.active = true; const bloodLayer = this.bloodPos?.layer ?? this.node.layer; gg.game.CurentBattle?.setLayerMask(booldNode, bloodLayer); this.boold = booldNode.getComponent(MonsterBlood); this.boold.Data = this.Data; this.boold.node.x = this.Data.type == MonsterType.BigBoss ? -100 : 0; this.boold.node.y = this.Data.type == MonsterType.BigBoss ? 40 : 0; this.updateBloodBar(); }); }; // if (this.Data.type == MonsterType.SmallMonster) name = "小怪血条底"; } this.updateBloodBar(); // if (this.Data.type == MonsterType.MidBoss || this.Data.type == MonsterType.GoldenBoss || this.Data.type == MonsterType.OrangeBoss || this.Data.type == MonsterType.SilverBoss) { // if (!this.boxEff) { // this.boxEff = gg.res.getNode('prefab/', '宝箱光', GBundle.BattleGameMonster); // if (this.boxEff) // this.boxEff.setParent(this.downEff); // } // } this._lastAttackTime = 0 this.IsDead = false; this._mapAgent.CanMove = this.isCanMove(); this._specialModelSpeedMultiple = 1;//特殊模式的速度倍数,目前都是1倍 this._reduceSpeedMultiple = 1 this._accelerateSpeedMultiple = 0 if (this.Data.id == 90004 || this.Data.id == 90005) { void this.loadUpEffPrefab('prefab/', '技能宝箱怪').then((boxGuideEff) => { if (!boxGuideEff) return; this.boxGuideEff = boxGuideEff; boxGuideEff.setPosition(0, 150, 0); boxGuideEff.active = this._canHideGuideEff; }); // this.scheduleOnce(() => { // this._canHideGuideEff = true; // }, 5); } this._showShield = false; this._totalShieldHP = 1; this._curShieldHP = 0; this.isKill = false; this.isKillByHp = false; this.playWalk() if (gg.game.CurentBattle?.curMap && isValid(gg.game.CurentBattle.curMap)) { this._mapAgent.curMap = gg.game.CurentBattle.curMap; } this.getAtkTarget(); if (this.Data.type == MonsterType.BigBoss) { let boss = this.node.getOrAddComponent(Boss); boss.setMonster(this); } } /**按 skin 实际显示大小重定位血条锚点 */ private syncBloodPosToSkin() { if (!this.bloodPos || !this.skin) return; const collider = this.node.getComponent(BoxCollider2D); if (!collider) return; const topPadding = this.Data?.type == MonsterType.SmallMonster ? 10 : 20; let y = collider.offset.y + collider.size.y * 0.5 + topPadding; if (this.Data.model == "空投怪物箱子" || this.Data.model == "伞兵" || this.Data.model == "女巫丧尸(加血)") y = y - 120; this.bloodPos.setPosition(0, y, 0); } protected update(dt: number): void { if (!gg.game.isBattleContextActive()) return; if (this.isMonsterPlayGude) { // 引导怪只做演出攻击,不参与寻路移动,避免出现“乱跳/抖动” if (this._mapAgent) { this._mapAgent.CanMove = false; } this.tryAttack(dt); return } if (gg.game.CurentBattle.IsGameOver) return; if (gg.game.CurentBattle.IsGamePause) return; if (!this.Data || this.Data.type == MonsterType.TreasureBox) { return } if (this.Data && this.Data.id == 40041) return const mapAgent = this._mapAgent; switch (this.state) { case MonsterState.MOVING_FORWARD: if (mapAgent) { mapAgent.MoveSpeed = this.getMoveSpeed(); // 控制(眩晕/粘液等)期间禁止移动;不可每帧 true 覆盖 resetControlBuff 的 false mapAgent.CanMove = this.isCanMove(); } break; case MonsterState.PATHFINDING: if (mapAgent) { mapAgent.MoveSpeed = this.getMoveSpeed(); mapAgent.CanMove = this.isCanMove(); } break; case MonsterState.ATTACKING: if (mapAgent) mapAgent.CanMove = false; this.tryAttack(dt); break; case MonsterState.STOP: if (mapAgent) mapAgent.CanMove = false; break; } } // 攻击玩家 private attack() { if (!this._atkTarget) return; if (gg.game.CurentBattle.IsGameOver) return; this.playStack(); if (!MTools.beforeTimes(100, "monster_atk_wall_sfx")) { gg.audio.playEffect({ name: "怪物攻击城墙" }); } let atk_multiple = gg.game.CurentBattle.CurentConfigChapter.atk_multiple; let atk = this.Data.attack * atk_multiple; if (gg.game.CurentBattle.BattleType == BattleType.InfiniteMode && gg.game.CurentBattle.InfiniteModeCurentWaveNum > 20) { let num = gg.data.table.getConst(ConstNumType.InfiniteModeAtkBaseMultiple) atk = atk * Math.pow(num, gg.game.CurentBattle.InfiniteModeCurentWaveNum - 20); } //取整数 atk = atk * gg.game.monsterAttackPercent; atk = Math.floor(atk); if (this.isMonsterPlayGude) { GEvent.Ins.emit(GEvent.playerHit, this._atkTarget) return } gg.game.CurentBattle.subWallHp(atk); GEvent.Ins.emit(GEvent.playerHit, this._atkTarget) } /**显示血条 */ showBloodBar() { if (this.boold) this.boold.node.active = true; } /**隐藏血条 */ hideBloodBar() { if (this.boold) this.boold.node.active = false; } /**更新血条 */ updateBloodBar() { if (this.boold) this.boold.updateBloodBar(this.hp, this.maxHp, this.shield, this.maxShield); } /**展示受击特效(受击特效) */ showBeAtkAni(weaponid?) { if (MTools.beforeTimes(BattlePerformance.hitVfxCooldownMs(), this.uuid)) return; if (gg.game.CurentBattle.BattleType == BattleType.BossMode && this.Data.type == MonsterType.BigBoss) { return; } if (!BattlePerformance.rollShowCombatVfx(0.48)) return; let conf = gg.data.project.getWeaponData(weaponid); let atkPosIndex = conf.atkPos; let [spineName, animName] = conf.hitEffect.split('|'); void gg.res.getNodeAsync('prefab/爆炸/', animName, GBundle.BattleGameWeapon).then((spineNode) => { if (!spineNode?.isValid || !this.upEff?.isValid) return; spineNode.setParent(this.upEff); const layer = this.upEff?.layer ?? this.node.layer; gg.game.CurentBattle?.setLayerMask(spineNode, layer); let pos = this.getPosByAtkPosIndex(atkPosIndex); spineNode.worldPosition = pos; const vfxMul = BattlePerformance.vfxScaleMul(); spineNode.setScale(vfxMul, vfxMul, vfxMul); let bombComp = spineNode.getComponent(BombSpine); bombComp.playSpine(spineName, () => { bombComp.destoryNode(); }, 1, false); }); } /** 直线武器瞄准用:碰撞盒中心世界坐标(缩小 Boss 包围盒后仍与可命中区域一致) */ getColliderAimWorldPos(): Vec3 { const ui = this.node.getComponent(UITransform); const collider = this.node.getComponent(BoxCollider2D); if (ui && collider) { return ui.convertToWorldSpaceAR(v3(collider.offset.x, collider.offset.y, 0)); } return this.node.worldPosition.clone(); } /**根据攻击点索引获取世界坐标 */ getPosByAtkPosIndex(atkPosIndex: number) { const ui = this.node.getComponent(UITransform); const collider = this.node.getComponent(BoxCollider2D); if (!ui || !collider) { return this.node.worldPosition.clone(); } // 使用“当前实例”碰撞体(已按怪物模型与缩放同步)计算头/腰/脚受击点 const halfH = collider.size.y * 0.5; const bottomY = collider.offset.y - halfH; const topY = collider.offset.y + halfH; const h = Math.max(1, topY - bottomY); // 1=头部 2=腰部 3=脚部 let localY = bottomY + h * 0.5; // 默认腰部 if (atkPosIndex == 1) { localY = bottomY + h * 0.82; } else if (atkPosIndex == 3) { localY = bottomY + h * 0.18; } return ui.convertToWorldSpaceAR(v3(collider.offset.x, localY, 0)); } /**获取移动速度 */ getMoveSpeed() { let reduceSpeedMultiple = this._reduceSpeedMultiple; if (this.Data.type == MonsterType.Boss) { reduceSpeedMultiple = this._reduceSpeedMultiple + gg.game.CurentBattle.fubenBoxReduceBossSpeed / 10000; } // 计算这一帧应该移动的距离 const moveDistance = this._speed / reduceSpeedMultiple / this._specialModelSpeedMultiple * (this._accelerateSpeedMultiple + this._redMoonSpeedMultiple + 1); return moveDistance; } // 尝试攻击玩家 private tryAttack(deltaTime: number) { if (this.isMonsterPlayGude) { this._lastAttackTime += deltaTime * gg.game.CurentBattle.TimeScale; if (this._lastAttackTime >= 1 / this.Data.attack_speed) { this._lastAttackTime = 0; this.attack(); } return } if (!gg.game.CurentBattle.canUpdateFrame()) return; if (!this._atkTarget) return; this._lastAttackTime += deltaTime * gg.game.CurentBattle.TimeScale; if (this._lastAttackTime >= 1 / this.Data.attack_speed) { this._lastAttackTime = 0; this.attack(); } } /**获取攻击目标 */ public getAtkTarget() { if (!this.Data || this.Data.type == MonsterType.TreasureBox) { return } const map = gg.game.CurentBattle?.curMap; if (!map || !isValid(map)) return; this._atkTarget = map.getPathShortestTarget(this.node.worldPosition); if (this._atkTarget) { this._mapAgent.tryAutoSetTargetFromTetraMap(); } } teleportToBornPos() { this.node.worldPosition = this._bornPos; this.state = MonsterState.PATHFINDING; this.getAtkTarget(); this.playWalk() } onMovePlayer() { this._canHideGuideEff = true; if (this.boxGuideEff && this.boxGuideEff.isValid) { this.boxGuideEff.active = true; this.scheduleOnce(() => { if (this.boxGuideEff && this.boxGuideEff.isValid) { this.boxGuideEff.active = false; } }, 20) } } /**被秒杀 */ private isKill = false; /**被斩杀 */ private isKillByHp = false; /**是否碰撞过怪物 */ private isColliderMonster = false; /**是否死亡 */ public IsDead: boolean = false; onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { // 物理接触调试:若你看到「节点坐标相距很远但仍触发 contact」 // 打开此开关看真正的接触点(manifold)与两个碰撞体的 AABB let skill: Skill = otherCollider.node.getComponent(Skill); const tetriLevel = (skill as any)?.tetriLevel ?? 1; let weaponData = skill.weaponData if (!weaponData) { return } if (this.isKill || this.isKillByHp || this.IsDead || skill.getIsDead()) return; // 弹射换靶后仍与上一只怪重叠:不能依赖 getColliderMonster,否则一清标记就会立刻再次命中同一只 if (skill.shouldIgnoreCatapultContactFrom(this.node.uuid)) return; if (skill.isWeapon20StraightPierce()) { if (skill.hasPierceHitMonster(this.node.uuid)) return; } else if (skill.getColliderMonster()) { return; } //子弹碰到怪物就消失 if (weaponData.flight == SkillType.直线弹幕 || weaponData.flight == SkillType.直线数量 || weaponData.flight == SkillType.直线目标持续伤害 || weaponData.flight == SkillType.直线穿透) { if (weaponData.weaponid == WeaponType.DanGong) { // if (!MTools.beforeTimes(100, "audio_timeStamp")) { // gg.audio.playEffect({ name: '彩虹飞弹命中', path: 'sound/武器音效/' }); // } } this.attackByWeapon(weaponData.weaponid, otherCollider.node, tetriLevel) if (skill.getSplitParent()) { skill.setColliderMonster(true) skill.removeBullet() } else if (skill.catapultCount > 0 && !skill.getIsSplitBullet()) { skill.setColliderMonster(true) Tween.stopAllByTarget(skill.node) skill.catapultCount-- const hitUuid = this.node.uuid this.scheduleOnce(() => { skill.findNextTarget(hitUuid) }, 0) } else if (skill.isWeapon20StraightPierce()) { skill.markPierceHitMonster(this.node.uuid) if (skill.subPiercingCount() <= 0) { skill.removeBullet() } } else { skill.setColliderMonster(true) const pierceLeft = skill.subPiercingCount() if (pierceLeft <= 0) { skill.removeBullet() } else { skill.setColliderMonster(false) } } } else if (weaponData.flight == SkillType.直线弹射) { this.attackByWeapon(weaponData.weaponid, otherCollider.node, tetriLevel) // 弹射次数在命中时扣减;避免 miss 时也扣次数导致异常 skill.setColliderMonster(true) let isSplitBullet = skill.getIsSplitBullet() //分裂子弹不弹射 // console.log('分裂子弹不弹射 isSplitBullet',isSplitBullet, skill.catapultCount) if (skill.catapultCount <= 0 || isSplitBullet) { skill.removeBullet() } else { Tween.stopAllByTarget(skill.node) skill.catapultCount-- const hitUuid = this.node.uuid this.scheduleOnce(() => { skill.findNextTarget(hitUuid) }, 0) } } else if (weaponData.flight == SkillType.直线分裂) { this.attackByWeapon(weaponData.weaponid, otherCollider.node) let originPos = selfCollider.node.worldPosition.clone() //如果是分裂的子弹,不继续分裂 let isSplitBullet = (otherCollider.node.getComponent('Skill') as any).getIsSplitBullet() if (!(otherCollider.node.getComponent('Skill') as any).getIsDead() && !isSplitBullet) { // console.log('穿天猴碰到怪物分裂两个子弹+++') let splitCount = 2 gg.game.CurentBattle.SkillController.splitBullet(weaponData.weaponid, originPos, splitCount, 0, 0, 0, tetriLevel) } if (skill.subPiercingCount() <= 0 && !skill.getIsDead()) { if (isValid(otherCollider.node)) { skill.removeBullet() } } } else if (weaponData.flight == SkillType.抛物线碰撞) { skill.setColliderMonster(true) this.attackByWeapon(weaponData.weaponid, otherCollider.node, tetriLevel) // 抛物线命中后仍继续运动;是否旋转由 ConstantSpeedParabola.enableRotation 控制 //(这里不再 stop tween,避免回调不执行) } else if (weaponData.flight == SkillType.抛物线没有碰撞) { //fix me skill.setColliderMonster(true) } } /**被武器攻击 */ attackByWeapon(weaponid: number, otherCollider?: Node, tetriLevel: number = 1, allAtkScale: number = 1) { let weaponPropertyData = BattleDataManager.getWeaponAddProperty(weaponid, this._allBuff, tetriLevel) let allAtk = weaponPropertyData.allAtk if (this.Data && this.Data.type == MonsterType.TreasureBox) { //宝箱增伤 allAtk = allAtk * (1 + gg.game.CurentBattle.fubenBoxAddDamage / 10000) } const atkMul = typeof allAtkScale === 'number' && Number.isFinite(allAtkScale) && allAtkScale > 0 ? allAtkScale : 1; if (atkMul !== 1) { allAtk *= atkMul; } //部分武器有控制时间,造成减速等效果 if (weaponPropertyData.controlTime > 0) { this.resetControlBuff(1, weaponPropertyData.controlTime, weaponid) } //冰棍减速特效 if (weaponPropertyData.reduceSpeedTime > 0 && weaponPropertyData.reduceSpeedRange > 0 && weaponid == (WeaponType as any).IceBar) { let reduceSpeed = Number(weaponPropertyData.reduceSpeedRange)//减速倍数 let reduceTime = Number(weaponPropertyData.reduceSpeedTime)//减速时间 this._reduceSpeedMultiple = 1 + reduceSpeed / 10000 this.resetIceBuff(reduceTime) } //跳跳糖自带感电特效 if (weaponPropertyData.reduceSpeedTime > 0 && weaponPropertyData.reduceSpeedRange > 0 && weaponid == (WeaponType as any).jumpSuger) { let addDamage = Number(weaponPropertyData.reduceSpeedRange)//伤害增幅倍数 let addDamageTime = Number(weaponPropertyData.reduceSpeedTime)//伤害增幅时间 this._damageMultiple = 1 + addDamage / 10000 this.resetElecBuff(addDamageTime) } //特殊技能 if (weaponPropertyData.specialSkillInfo.length > 0) { this.attackBySpecialWeapon(weaponid, weaponPropertyData.specialSkillInfo, allAtk, weaponPropertyData, otherCollider) } //特殊技能buff 减速,燃烧等 //console.log('当前武器特殊技能buff==',weaponPropertyData.specialSkillAddBuffInfo) if (weaponPropertyData.specialSkillAddBuffInfo.length > 0) { this.attackBySpecialWeaponBuff(weaponid, weaponPropertyData.specialSkillAddBuffInfo) } this.showBeAtkAni(weaponid) this.subHP(weaponid, allAtk, weaponPropertyData.isCrit, weaponPropertyData.addRateAtk) } /**被特殊武器攻击 */ attackBySpecialWeapon(weaponid, specialSkillInfo, allAtk, weaponPropertyData, otherCollider?: Node,) { for (let i = 0; i < specialSkillInfo.length; i++) { let buffInfo = specialSkillInfo[i] let arr = buffInfo.split(',') let buffType = Number(arr[0]) //分裂子弹 if (buffType == SpecialSkillType.divideBullet || buffType == SpecialSkillType.splitBulletWithContinueTime) { if (!isValid(otherCollider)) { continue } //如果是分裂的子弹,不继续分裂 let isSplitBullet = (otherCollider.getComponent('Skill') as any).getIsSplitBullet() if (isSplitBullet) { //console.log('分裂子弹不继续分裂') continue } // console.log('分裂子弹',buffInfo) //分裂子弹 if (!this.isColliderMonster) { // console.log('彩虹飞弹碰到怪物分裂三个子弹+++',new Date().getTime()) let splitCount = Number(arr[1]); let damageRate = Number(arr[2]) || 0; //伤害比例 let stayTime = Number(arr[3]) || 0; let damageRadius = Number(arr[4]) || 0; //设置分裂的母体 otherCollider.getComponent(Skill).setSplitParent(true) //分裂子弹 let originPos = otherCollider.worldPosition.clone() const skSplit = otherCollider.getComponent(Skill); const tetriLv = skSplit?.tetriLevel ?? 1; gg.game.CurentBattle.SkillController.splitBullet(weaponid, originPos, splitCount, stayTime, damageRate, damageRadius, tetriLv) } //同一单位伤害递增 } else if (buffType == SpecialSkillType.damageIncrease) { let rate = Number(arr[1]) / 10000//伤害增加倍数 let maxTimes = Number(arr[2])//最大增加次数 //记录被同种武器攻击次数 let atkCount = this._sameWeaponAtkCount.get(weaponid) || 0 atkCount++ if (atkCount > maxTimes) { atkCount = maxTimes } this._sameWeaponAtkCount.set(weaponid, atkCount) //同一单位伤害递增 this._sameWeaponAtkMultiple = 1 + rate * atkCount //范围越来越大,伤害也越来越大 } else if (buffType == SpecialSkillType.moreLarge) { let largeCount = Number(arr[1])//模型增加倍数 let damageInterval = Number(arr[2])//x秒增加一次伤害 let rate = Number(arr[3]) / 10000//伤害增加倍数 let aliveTime = (otherCollider.getComponent('Skill') as any).getAliveTime() let addTime = Math.floor(aliveTime / damageInterval * rate * 100) / 100 this._damageSpecialMultiple = 1 + addTime } else if (buffType == SpecialSkillType.extraDamage) { //额外伤害 let damageTimes = Number(arr[1]) / 10000//伤害倍数 //添加一个技能表现 作用一次就行 let repeatCount = 1 //扣血 //播放动画anim fix me this.playColaBomb('符文爆炸', this.node.position.clone(), 1) this.subHP(weaponid, allAtk * damageTimes, weaponPropertyData.isCrit, weaponPropertyData.addRateAtk) } else if (buffType == SpecialSkillType.extraExplode) { let damageRadius = Number(arr[1])//伤害半径 let damageTimes = Number(arr[2])//伤害倍数 // let fallMonsters = gg.game.CurentBattle.MonsterSpawner.getMonstersInArea(this.node.worldPosition.clone(), damageRadius) //播放一次爆炸 this.playColaBomb('符文爆炸', this.node.position.clone(), 1) for (let i = 0; i < fallMonsters.length; i++) { let monster = fallMonsters[i] monster.getComponent(Monster).subHP(weaponid, allAtk * damageTimes, weaponPropertyData.isCrit, weaponPropertyData.addRateAtk) } } } } /**符文爆炸 */ public playColaBomb(bombSpine: string, pos: Vec3, scale: number) { if (!BattlePerformance.rollShowCombatVfx()) return; const mapNode = gg.game.CurentBattle?.curMap?.node; if (!mapNode?.isValid) return; const localPos = pos.clone(); // 碰撞回调内勿同步改节点树;且动态加载下 getNode 可能尚未命中 prefab void gg.res.getNodeAsync(`prefab/爆炸/`, bombSpine, GBundle.BattleGameWeapon).then((spineNode) => { if (!spineNode?.isValid || !mapNode?.isValid) return; spineNode.setParent(mapNode); spineNode.setPosition(localPos); const adj = BattlePerformance.adjustVfxScale(scale); spineNode.scale = v3(adj, adj, 0); const bombComp = spineNode.getComponent(BombSpine); if (!bombComp) { gg.res.putNode(spineNode); return; } bombComp.playSpine('play', () => { bombComp.destoryNode(); }, 1, false); }); } /**生成爆炸Component */ public createBombComponent(bombSpine: string, pos: Vec3, scale: number, parentNode: Node = null, allowSkip = true) { if (allowSkip && !BattlePerformance.rollShowCombatVfx()) { return null; } let spineNode = gg.res.getNode(`prefab/爆炸/`, `${bombSpine}`, GBundle.BattleGameWeapon) if (!spineNode) { console.warn('[Monster] bombSpine 未就绪=', bombSpine); return null; } const parent = parentNode?.isValid ? parentNode : gg.game.CurentBattle?.curMap?.node; if (!parent?.isValid) { gg.res.putNode(spineNode); return null; } spineNode.setParent(parent); spineNode.setPosition(pos) const adj = BattlePerformance.adjustVfxScale(scale); spineNode.scale = v3(adj, adj, 0) let bombComp = spineNode.getComponent(BombSpine); return bombComp } /**被特殊武器buff攻击 */ attackBySpecialWeaponBuff(weaponid, specialSkillAddBuffInfo) { //console.log('被特殊武器buff攻击', specialSkillAddBuffInfo) for (let i = 0; i < specialSkillAddBuffInfo.length; i++) { let buffInfo = specialSkillAddBuffInfo[i] let arr = buffInfo.split(',') let buffType = Number(arr[0]) if (buffType == SpecialSkillAddBuffType.burn) { //燃烧 let interval = Number(arr[1])//x秒造成一次伤害 let damageTimes = Number(arr[2]) / 10000//每次造成的伤害倍数 let allTime = Number(arr[3])//总时间 //概率 let rate = Number(arr[4]) / 10000//概率挂上buff if (rate >= 1) { let repeatCount = Math.ceil(allTime / interval)//造成伤害次数 this.resetBurnBuff(weaponid, damageTimes, repeatCount, interval) } else { if (Math.random() <= rate) { let repeatCount = Math.ceil(allTime / interval)//造成伤害次数 this.resetBurnBuff(weaponid, damageTimes, repeatCount, interval) } } } else if (buffType == SpecialSkillAddBuffType.reduceSpeed) { //减速 let reduceSpeed = Number(arr[1])//减速倍数 let reduceTime = Number(arr[2])//减速时间 //概率 let rate = Number(arr[3])//减速概率 if (Math.random() * 10001 <= rate) { this._reduceSpeedMultiple = 1 + reduceSpeed / 10000 this.resetIceBuff(reduceTime) } } else if (buffType == SpecialSkillAddBuffType.elec) { //伤害增幅 let addDamage = Number(arr[1])//伤害增幅倍数 let addDamageTime = Number(arr[2])//伤害增幅时间 //概率 let rate = Number(arr[3])//感电概率 if (Math.random() * 10001 <= rate) { this._damageMultiple = 1 + addDamage / 10000 this.resetElecBuff(addDamageTime) } } else if (buffType == SpecialSkillAddBuffType.control) { //眩晕 let controlRate = Number(arr[1])//控制概率 let controlTime = Number(arr[2])//控制时间 if (Math.random() <= controlRate / 10000) { this.resetControlBuff(1, controlTime, weaponid) } } else if (buffType == SpecialSkillAddBuffType.kill) { //秒杀 let killRate = Number(arr[1])//秒杀几率 if (Math.random() <= killRate / 10000) { //秒杀成功 this.onKill(); } } else if (buffType == SpecialSkillAddBuffType.killHp) { //斩杀 let killRate = Number(arr[1])//斩杀血线 if (this.hp / this.maxHp <= killRate / 10000) { //斩杀成功 this.onKillForHp(); } } else if (buffType == SpecialSkillAddBuffType.blood) { //流血 let interval = Number(arr[1])//x秒造成一次伤害 let damageTimes = Number(arr[2]) / 10000 //每次造成的伤害倍数 let allTime = Number(arr[3])//总时间 let repeatCount = Math.ceil(allTime / interval)//造成伤害次数 this.resetBloodBuff(weaponid, damageTimes, repeatCount, interval) } } } /**被秒杀 */ onKill() { //BOSS不会被秒杀 if (this.Data.type == MonsterType.MidBoss || this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss) return; this.isKill = true; this.showKillEff(1); } /**被斩杀 */ onKillForHp() { //BOSS不会被秒杀 if (this.Data.type == MonsterType.MidBoss || this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss) return; //斩杀成功 this.isKillByHp = true; this.showKillEff(2); } /**获取冰棒武器的旋转速度 */ private getIceWeaponRotateSpeed(): number { // 从SkillController获取冰棒武器的旋转速度 const skillController = gg.game.CurentBattle.SkillController; const iceWeaponScheduleScr = skillController.node.getComponent('iceWeaponSchedule') as any; if (iceWeaponScheduleScr) { return iceWeaponScheduleScr.rotateSpeed; } // 默认旋转速度 return 1; } /**重置控制buff */ resetControlBuff(repeatCount, interval, weaponid) { let nameStr = weaponid == (WeaponType as any).NiuPiTang ? '怪_粘液' : '怪_晕眩' const apply = (spine: Node) => { this._controlSpine = spine; spine.active = true; this._mapAgent.CanMove = false; this.addBuff(SpecialSkillAddBuffType.control); let com = this.node.getComponent(BuffControlSchedule); if (!com) { com = this.node.addComponent(BuffControlSchedule); } com.resetControlBuff(interval, repeatCount); }; if (this._controlSpine) { apply(this._controlSpine); return; } void this.loadUpEffPrefab("prefab/", nameStr).then((node) => node && apply(node)); } private _controlSpine: Node = null; /**重置燃烧buff */ resetBurnBuff(weaponid, damageTimes, repeatCount, interval) { const apply = (spine: Node) => { this._burnSpine = spine; spine.active = true; this.addBuff(SpecialSkillAddBuffType.burn); let com = this.node.getComponent(BuffBurnSchedule); if (!com) { com = this.node.addComponent(BuffBurnSchedule); } com.resetFireBuff(weaponid, damageTimes, interval, repeatCount); }; if (this._burnSpine) { apply(this._burnSpine); return; } void this.loadUpEffPrefab("prefab/", "怪_火").then((node) => node && apply(node)); } /**重置掉血buff */ resetBloodBuff(weaponid, damageTimes, interval, repeatCount) { const apply = (spine: Node) => { this._burnSpine = spine; spine.active = true; this.addBuff(SpecialSkillAddBuffType.burn); let com = this.node.getComponent(BuffBurnSchedule); if (!com) { com = this.node.addComponent(BuffBurnSchedule); } com.resetFireBuff(weaponid, damageTimes, interval, repeatCount); }; if (this._burnSpine) { apply(this._burnSpine); return; } void this.loadUpEffPrefab("prefab/", "怪_掉血").then((node) => node && apply(node)); } private _burnSpine: Node = null; /**重置冰霜buff */ resetIceBuff(reduceTime, reduceSpeed = 0, isNew = true) { if (reduceSpeed != 0) { this._reduceSpeedMultiple = 1 + reduceSpeed / 10000 } const apply = (spine: Node) => { this._reduceSpeedSpine = spine; spine.active = true; this.addBuff(SpecialSkillAddBuffType.reduceSpeed); if (reduceTime != -1) { let com = this.node.getComponent(BuffIceSchedule); if (!com) { com = this.node.addComponent(BuffIceSchedule); } com.resetIceBuff(reduceTime, 1, this._reduceSpeedSpine); } }; if (this._reduceSpeedSpine) { apply(this._reduceSpeedSpine); return; } void this.loadUpEffPrefab("prefab/", "怪_冰").then((node) => node && apply(node)); } private _reduceSpeedSpine: Node = null; /**重置加速buff(加速倍数,加速时间) */ resetAccelerateBuff(speedTimes, buffTime) { const apply = (spine: Node) => { this._accSpine = spine; spine.active = true; this.addBuff(SpecialSkillAddBuffType.accelerate); let com = this.node.getOrAddComponent(BuffAccelerateSchedule); com.resetAccelerateBuff(buffTime, 1, this._accSpine); this._accelerateSpeedMultiple = speedTimes; }; if (this._accSpine) { apply(this._accSpine); return; } void this.loadUpEffPrefab("prefab/", "怪_加速").then((node) => node && apply(node)); } private _accSpine: Node = null; /**重置感电buff */ resetElecBuff(addDamageTime) { const apply = (spine: Node) => { this._elecSpine = spine; spine.active = true; this.addBuff(SpecialSkillAddBuffType.elec); if (addDamageTime != -1) { let com = this.node.getComponent(BuffElecSchedule); if (!com) { com = this.node.addComponent(BuffElecSchedule); } com.resetElecBuff(addDamageTime, 1); } }; if (this._elecSpine) { apply(this._elecSpine); return; } void this.loadUpEffPrefab("prefab/", "怪_电").then((node) => node && apply(node)); } private _elecSpine: Node = null; getAllBuff() { return this._allBuff } addBuff(buffType) { if (this._allBuff.indexOf(buffType) == -1) { this._allBuff.push(buffType) } } removeBuff(buffType) { let index = this._allBuff.indexOf(buffType) if (index != -1) { this._allBuff.splice(index, 1) } if (buffType == SpecialSkillAddBuffType.reduceSpeed) { this._reduceSpeedMultiple = 1 if (this._reduceSpeedSpine) { gg.res.putNode(this._reduceSpeedSpine); this._reduceSpeedSpine = null; } } else if (buffType == SpecialSkillAddBuffType.elec) { this._damageMultiple = 1 if (this._elecSpine) { gg.res.putNode(this._elecSpine); this._elecSpine = null; } } else if (buffType == SpecialSkillAddBuffType.control) { // 控制结束:按当前状态恢复是否可移动(避免攻击/停顿时误开移动) if (this._mapAgent) { this._mapAgent.CanMove = this.isCanMove(); } if (this._controlSpine) { gg.res.putNode(this._controlSpine); this._controlSpine = null; } } else if (buffType == SpecialSkillAddBuffType.accelerate) { this._accelerateSpeedMultiple = 0 if (this._accSpine) { gg.res.putNode(this._accSpine); this._accSpine = null; } } else if (buffType == SpecialSkillAddBuffType.burn) { if (this._burnSpine) { gg.res.putNode(this._burnSpine); this._burnSpine = null; } } } isCanMove() { if (this.Data.id == 40041) return false; if (this.state !== MonsterState.MOVING_FORWARD && this.state !== MonsterState.PATHFINDING) return false; const isControlBuff = this._allBuff.indexOf(SpecialSkillAddBuffType.control) !== -1; if (isControlBuff) return false; if (this._isShowWenHaoEff) return false; return true; } /**怪物掉血(武器id,掉血值,是否暴击,伤害倍率,是否炸弹道具) */ subHP(weaponId: number, hp: number, isCrit: boolean, addRateAtk: number = 1, isBombProp: boolean = false) { if (hp <= 0) return // 保护期间:受到伤害为 0 if (this._isProtecting) return let trueHp = Math.floor(hp * this._damageMultiple * this._sameWeaponAtkMultiple * this._damageSpecialMultiple) if (isBombProp) { trueHp = hp } let haveHp = trueHp - this._curShieldHP; if (this._curShieldHP > 0) { this._curShieldHP -= trueHp; this.updateShield(); if (this._curShieldHP <= 0) { this.closeShield(); } } if (this.isKill || this.isKillByHp) { trueHp = this.maxHp - this.hp; this.hp -= this.maxHp; } else if (haveHp > 0) { this.hp -= trueHp; } gg.PRR.PlayerDamageTotal += trueHp this.showSubHpNum(trueHp, isCrit, addRateAtk) //setBooldNum if (this.hp <= 0) { this.hp = 0; this.die(); } else { this.showBeAtkEffect() } this.setTag("weaponAtkCount" + weaponId, true); this.updateBloodBar(); //gg.game.CurentBattle.DamageStatistics.updateWeaponDamage(weaponId, trueHp) } /**加血 * @param hp 加血值(如果是百分比,则加总血量的百分比) * @param isCoefficient 是否是百分比加血 */ addHP(hp: number, isCoefficient: boolean) { if (isCoefficient) { hp = Math.floor(this.maxHp * hp) } this.hp += hp; if (this.hp > this.maxHp) { this.hp = this.maxHp; } this.clearAddHpEff(); void (async () => { if (!this.addHpEff1?.isValid) { this.addHpEff1 = await this.loadUpEffPrefab("prefab/", "怪物加血"); } if (!this.addHpEff1?.isValid) return; this.addHpEff1.position = Vec3.ZERO; this.addHpEff1.active = true; this.addHpEff1.getComponent(sp.Skeleton).setAnimation(0, '加血怪物上层', false); if (!this.addHpEff2?.isValid) { this.addHpEff2 = await gg.res.getNodeAsync("prefab/", "怪物加血", GBundle.BattleGameMonster); if (this.addHpEff2?.isValid) { this.addHpEff2.setParent(this.upEff); } } if (!this.addHpEff2?.isValid) return; this.addHpEff2.position = Vec3.ZERO; this.addHpEff2.active = true; this.addHpEff2.getComponent(sp.Skeleton).setAnimation(0, '加血怪物下层', false); this.addHpEff2.setSiblingIndex(0); })(); this.scheduleOnce(() => { this.clearAddHpEff(); }, 2) this.updateBloodBar(); } /**清除加血特效 */ clearAddHpEff() { if (this.addHpEff1 && this.addHpEff1.isValid) gg.res.putNode(this.addHpEff1); if (this.addHpEff2 && this.addHpEff2.isValid) gg.res.putNode(this.addHpEff2); this.addHpEff1 = null; this.addHpEff2 = null; } addHpEff1: Node = null; addHpEff2: Node = null; /**显示受击效果(怪物变色) */ showBeAtkEffect() { if (MTools.beforeTimes(100, "_hurtColorTimeStamp" + this.uuid)) return; Tween.stopAllByTarget(this.anim) tween(this.anim) .call(() => { if (isValid(this.anim)) { this.anim.color = new Color(255, 83, 83, 255) } }) .delay(0.35) .call(() => { if (isValid(this.anim)) { this.anim.color = new Color(255, 255, 255, 255) } }) .start() } /**显示伤害值 */ async showSubHpNum(hp: number, isCrit: boolean, addRateAtk: number = 1) { if (MTools.beforeTimes(150, "_hurtScoreTimeStamp" + this.uuid)) return if (!acquireWeChatIosHurtFloatSpawn()) return addRateAtk = 1 const scoreLayer = gg.game.CurentBattle?.ScoreLayer; if (!scoreLayer?.isValid) return; const bloodPos = this.node.getChildByName('bloodPos'); if (!bloodPos?.isValid) return; let worldPos = bloodPos.worldPosition.clone() let scoreName = addRateAtk <= 1 ? (isCrit ? 'critHurtScore' : 'atkMonsterScore') : 'rateDamageScore' let scoreNode = await gg.res.getNodeAsync('prefab/数字/', scoreName, GBundle.tetriGame); if (!scoreNode?.isValid) return; scoreNode.setParent(scoreLayer); gg.game.CurentBattle.setLayerMask(scoreNode, scoreLayer.layer); scoreNode.active = true; scoreNode.opacity = 255; Tween.stopAllByTarget(scoreNode); scoreNode.worldPosition = worldPos scoreNode.getComponent(HurtScore).showHurtNum(hp, isCrit, addRateAtk > 1); let index = gg.game.CurentBattle.ScoreLayer.children.findLastIndex((item) => item.name == scoreName); if (index != -1) { scoreNode.setSiblingIndex(index); } //} } /**显示秒杀/斩杀飘字1秒杀,2斩杀 */ async showKillEff(type: number) { const scoreLayer = gg.game.CurentBattle?.ScoreLayer; if (!scoreLayer?.isValid) return; const bloodPos = this.node.getChildByName('bloodPos'); if (!bloodPos?.isValid) return; let worldPos = bloodPos.worldPosition.clone() let scoreNode = await gg.res.getNodeAsync('prefab/', 'killEff', GBundle.tetriGame) if (!scoreNode?.isValid) return; scoreNode.setParent(scoreLayer); gg.game.CurentBattle.setLayerMask(scoreNode, scoreLayer.layer); scoreNode.active = true; scoreNode.opacity = 255; Tween.stopAllByTarget(scoreNode); scoreNode.worldPosition = worldPos; scoreNode.find("秒杀").active = type == 1; scoreNode.find("斩杀").active = type == 2; let index = gg.game.CurentBattle.ScoreLayer.children.findLastIndex((item) => item.name == 'killEff'); if (index != -1) { scoreNode.setSiblingIndex(index); } tween(scoreNode) .set({ opacity: 255 }) .to(0.1, { scale: new Vec3(2, 2, 2) }) .to(0.25, { scale: new Vec3(1, 1, 1) }) .to(0.12, { opacity: 0 }) .call(() => { gg.res.putNode(scoreNode) }) .start(); } /**记录摆放障碍物前的位置 */ recordSetObstacleBeforePos() { this._setObstacleBeforePos = this.node.worldPosition.clone() } /**还原到摆放障碍物前的位置 */ resetSetObstacleBeforePos() { if (!this._setObstacleBeforePos) return this.node.worldPosition = this._setObstacleBeforePos } /**获取摆放障碍物前的位置 */ getSetObstacleBeforePos() { return this._setObstacleBeforePos } /**怪物是否死亡 */ getIsDead() { return this.IsDead } /**获取怪物总血量 */ getTotalHP() { return this.maxHp } /**获取怪物类型 */ getMonsterType() { return this.Data.type } /** * 埋点用:当前剩余生命占最大生命的百分比(0–100,四舍五入)。 * 存在护盾时按 (本体血 + 当前盾) / (最大本体血 + 护盾上限) 计算。 */ getRemainingHealthPercentForReport(): number { if (this.IsDead || !this.Data || !(this.maxHp > 0)) return 0; let numer = this.hp; let denom = this.maxHp; if (this._totalShieldHP > 1) { numer = this.hp + this._curShieldHP; denom = this.maxHp + this._totalShieldHP; } if (!(denom > 0)) return 0; return Math.max(0, Math.min(100, Math.round((100 * numer) / denom))); } /**加盾 * @param v 护盾血量系数 */ addShieldHp(v: number) { if (this.hp <= 0) return; this._showShield = true; this._totalShieldHP = Math.floor(this.maxHp * v); if (this._totalShieldHP <= 0) this._totalShieldHP = 1; this._curShieldHP = this._totalShieldHP; this.updateShield(); if (!this._shielEffNode) { void this.loadUpEffPrefab('prefab/', '怪物护盾').then((node) => { if (!node) return; this._shielEffNode = node; this._applyShieldEffLayout(); }); } else { this._shielEffNode.active = true; this._applyShieldEffLayout(); } } private _applyShieldEffLayout() { if (!this._shielEffNode?.isValid) return; this._shielEffNode.active = true; if (this.Data.type == MonsterType.SmallMonster) { this._shielEffNode.x = 0; this._shielEffNode.y = -30; } else if (this.Data.type == MonsterType.Boss) { this._shielEffNode.x = -30; this._shielEffNode.y = 0; } else { this._shielEffNode.x = -15; this._shielEffNode.y = -30; } } /**更新盾 */ updateShield() { let shields = this.boold.node.find("shields"); if (shields) { // if (this._showShield) { // bloodNode.active = true; // } if (this._showShield && !shields.active) { shields.active = true; } let anim = shields.getComponentInChildren(sp.Skeleton); let progress = shields.find("shieldsProgress").getComponent(Sprite); if (shields.active) { progress.fillRange = clamp01(this._curShieldHP / this._totalShieldHP); } if (this._showShield) { anim.setAnimation(0, "待机", true); } else { anim.setAnimation(0, "裂开消失", false); anim.setCompleteListener(() => { anim.setCompleteListener(null); if (shields.isValid) shields.active = false; }) } } } /**关闭护盾 */ closeShield() { this._showShield = false; if (this._shielEffNode) { this._shielEffNode.active = false; } this.updateShield(); } _curShieldHP: number = 0; _totalShieldHP: number = 1; _showShield: boolean = false; _shielEffNode: Node = null; // 怪物死亡 public async die() { if (this.IsDead) return; this.IsDead = true; if (gg.game.CurentSelectBattleType == BattleType.BossMode && (this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss)) { gg.game.CurentBattle.finishKillBoss = true; } if (gg.game.CurentBattle.CaptureZombieId != 0 && this.Data.id == gg.game.CurentBattle.CaptureZombieId && !gg.game.CurentBattle.isCaptureZombie) { //发消息 gg.game.CurentBattle.isCaptureZombie = true let worldPosi = this.node.worldPosition.clone() GEvent.Ins.emit(GEvent.captureZombieSuccess, worldPosi) } if (!this.isMonsterPlayGude) { gg.game.CurentBattle.monsterDie(this); // 胜利结算时 checkWin 会把 IsGameOver 置 true;仍必须 remove,否则节点留在场上 if (!gg.game.CurentBattle.IsGameOver && this.Data.coin > 0) { GEvent.Ins.emit(GEvent.showCoinFly, this.node.worldPosition.clone()); } } else { this.skin.scale_x = 1; } this.isMonsterPlayGude = false; //console.log('怪物死亡,放入对象池==') this.remove(); } /**移除怪物 */ remove() { this.IsDead = true; this.clearAddHpEff(); //this.node.active = false; //console.log('怪物死亡,放入对象池=='); this.removeBuff(SpecialSkillAddBuffType.burn); this.removeBuff(SpecialSkillAddBuffType.reduceSpeed); this.removeBuff(SpecialSkillAddBuffType.elec); this.removeBuff(SpecialSkillAddBuffType.control); this.removeBuff(SpecialSkillAddBuffType.accelerate); this.node.getComponent(BuffBurnSchedule)?.removeSelf(); this.node.getComponent(BuffAccelerateSchedule)?.removeSelf(); this.node.getComponent(BuffControlSchedule)?.removeSelf(); this.node.getComponent(BuffElecSchedule)?.removeSelf(); this.node.getComponent(BuffIceSchedule)?.removeSelf(); this.node.getComponent(SingleWitch)?.destroy(); this.clear(); // 避免在物理 contact 回调里直接切换节点/刚体激活状态导致警告: // "Can not active RigidBody in contact listener." // 延迟到下一帧再回收进对象池。 const n = this.node; this.scheduleOnce(() => { if (isValid(n)) { gg.res.putNode(n); } }, 0); this._showShield = false; } clearNode() { this.node.destroy() } setBloodMoon(show: boolean) { this._redMoonSpeedMultiple = show ? gg.game.CurentBattle.RedMoonMultiplier : 0; if (show) { if (!this._accSpine) { void this.loadUpEffPrefab('prefab/', '怪_加速').then((node) => { if (!node) return; this._accSpine = node; this._accSpine.active = true; }); } else { this._accSpine.active = true; } } if (!show && this._allBuff.indexOf(SpecialSkillAddBuffType.accelerate) == -1) { if (this._accSpine) { gg.res.putNode(this._accSpine); this._accSpine = null; } } } showWenHaoEff() { void gg.res.getNodeAsync('prefab/', '问号', GBundle.tetriGame).then((wenhao) => { if (!wenhao?.isValid || !this.upEff?.isValid) return; wenhao.setParent(this.upEff); wenhao.active = true; wenhao.worldPosition = this.bloodPos.worldPosition.clone().add(v3(0, 20, 0)); Tween.stopAllByTarget(wenhao); wenhao.scale = v3(0, 0, 0); this._isShowWenHaoEff = true; tween(wenhao) .to(0.3, { scale: new Vec3(1, 1, 1) }, { easing: easing.bounceOut }) .delay(1) .call(() => { if (wenhao && wenhao.isValid && wenhao.parent) gg.res.putNode(wenhao); this._isShowWenHaoEff = false; }).start(); }); } private _isShowWenHaoEff = false; /**攻击动效 */ playStack() { if (!this.Data || this.Data.type == MonsterType.TreasureBox) { return } this.playSpine("攻击", null, 1, false); } /**走路动效 */ playWalk(speed = 1) { if (!this.Data || this.Data.type == MonsterType.TreasureBox) { return } if (gg.game.CurentSelectBattleType == BattleType.BossMode) { let count = gg.game.CurentBattle.curFailReviveLeftNum; speed = count < 3 ? 1 : 0.5; if (this._allBuff.indexOf(SpecialSkillAddBuffType.reduceSpeed) >= 0) { speed *= 0.5; } } this.playSpine("走路", null, speed); } /**降落动效 */ playFall() { if (!this.Data || this.Data.type == MonsterType.TreasureBox) { return } this.playSpine("1降落", null, 1, false); } /**落地动效 */ playLand() { if (!this.Data || this.Data.type == MonsterType.TreasureBox) { return } this.playSpine("2落地", null, 1, false); } /** * 播放 Spine 动画 * @param name 动画名称 * @param callback 动画播放完成后的回调函数 * @param timeScale 动画播放速度倍率 * @param loop 是否循环播放 */ private playSpine(name: string, callback: Function = null, timeScale = 1, loop = true) { // 节点或动画组件缺失时直接跳过,防止特殊怪(无 Spine)的报错 if (!this.node || !this.anim) { console.warn("[Monster] playSpine skipped, anim missing. name =", name, "monsterId =", this.Data?.id); return; } // 如果当前正在播放的动画是“死亡”,则不执行任何操作 if (this.anim.animation == "死亡") return; // 设置动画播放完成后的回调函数 this.anim.setCompleteListener(() => { if (callback) callback(); }); // 设置动画播放速度倍率,考虑了战斗速度的影响 this.anim.timeScale = timeScale * gg.game.CurentBattle.TimeScale; // 播放指定名称的动画,并设置是否循环播放 this.anim.setAnimation(0, name, loop); } /**清除资源和引用以及状态等 */ clear() { this._tagStatus.clear(); if (this.boold) { gg.res.putNode(this.boold.node); this.boold = null; } if (this.skin.children.length > 0) { gg.res.putNode(this.skin.children[0]); } for (let i = this.downEff.children.length - 1; i >= 0; i--) { let item = this.downEff.children[i]; gg.res.putNode(item); } for (let i = this.upEff.children.length - 1; i >= 0; i--) { let item = this.upEff.children[i]; gg.res.putNode(item); } this.boxEff = null; this._accSpine = null; this._reduceSpeedSpine = null; this._elecSpine = null; this._controlSpine = null; this._burnSpine = null; this.boxGuideEff = null; } setSkinScale(scale) { this.skin.scale_x = scale } }