import { _decorator, Component, find, isValid, Layers, Node, sp, UITransform, v3, Vec3 } from 'cc'; import { SpriteLoad } from '../../mx/components/SpriteLoad'; import { GBundle, GPath } from '../game/ConfigRes'; import { shootPoint } from './shootPoint'; import { GEvent } from '../../mx/module/event/GEvent'; import { SkillType } from '../ui/BattleGame/WeaponSkill/SkillTypes'; import { IWeaponProperty, SpecialSkillType } from '../ui/BattleGame/BattleDataManager'; import { Skill } from '../ui/BattleGame/WeaponSkill/Skill'; import { LaserLine } from '../ui/BattleGame/WeaponSkill/LaserLine'; import { tetriNode } from './tetriNode'; import { role } from './role'; import { WeaponType } from '../manager/WeaponDataManager'; import { BattleType } from '../manager/ChapterDataManager'; import { UILayer } from '../../mx/module/ui/UIManager'; /** 救救我鸭:场上角色初始武器 id */ export const JIUJIUWOYA_INIT_WEAPON_ID = 1; const { ccclass, property } = _decorator; /**方块武器载体脚本,用于绑定武器和技能 * TetriType == 1 基础武器方块,只有武器和技能 * TetriType == 5 藤蔓方块,既有武器还有其他特殊功能 */ @ccclass('tetriWeaponCarrier') export class tetriWeaponCarrier extends Component { /** 攻击动画相对首个弹体/技能生成提前的秒数(先播「攻击」再发射) */ private static readonly ATK_ANIM_LEAD_SEC = 0.4; private static readonly ATK_ANIM_LEAD_SEC2 = 0.1; roleConfig = [ { id: 1, name: "Tetris_att_I", mainPosi: { x: -0.308, y: -10.809 }, animSkin: '小紫', scalex: -0.25, scaley: 0.25 }, { id: 2, name: "Tetris_att_1_1", mainPosi: { x: -1.484, y: -12.331 }, animSkin: '小紫', scalex: -0.25, scaley: 0.25 }, { id: 3, name: "Tetris_att_J1", mainPosi: { x: -28.877, y: -25.077 }, animSkin: '小蓝', scalex: -0.25, scaley: 0.25 }, { id: 4, name: "Tetris_att_J2", mainPosi: { x: -13.884, y: 23.864 }, animSkin: '小蓝', scalex: -0.25, scaley: 0.25 }, { id: 5, name: "Tetris_att_J3", mainPosi: { x: 33.67, y: 5.42 }, animSkin: '小蓝', scalex: -0.25, scaley: 0.25 }, { id: 6, name: "Tetris_att_J", mainPosi: { x: 16.683, y: -43.542 }, animSkin: '小蓝', scalex: -0.25, scaley: 0.25 }, { id: 7, name: "Tetris_att_L1", mainPosi: { x: -29.799, y: 4.73 }, animSkin: '小橙', scalex: -0.25, scaley: 0.25 }, { id: 8, name: "Tetris_att_L2", mainPosi: { x: 15.513, y: 21.501 }, animSkin: '小橙', scalex: -0.25, scaley: 0.25 }, { id: 9, name: "Tetris_att_L3", mainPosi: { x: 33.276, y: -29.162 }, animSkin: '小橙', scalex: -0.25, scaley: 0.25 }, { id: 10, name: "Tetris_att_L", mainPosi: { x: -14.952, y: -44.351 }, animSkin: '小橙', scalex: -0.25, scaley: 0.25 }, { id: 11, name: "Tetris_att_Z1_1", mainPosi: { x: 2.397, y: -12.412 }, animSkin: '小憨', scalex: -0.25, scaley: 0.25 }, { id: 12, name: "Tetris_att_Z1", mainPosi: { x: -0.035, y: -9.153 }, animSkin: '小憨', scalex: -0.2, scaley: 0.2 }, { id: 13, name: "Tetris_att_Z2_1", mainPosi: { x: 16.189, y: -10.274 }, animSkin: '小红', scalex: -0.25, scaley: 0.25 }, { id: 14, name: "Tetris_att_Z2", mainPosi: { x: -0.057, y: -12.142 }, animSkin: '小红', scalex: -0.25, scaley: 0.25 }, { id: 15, name: "Tetris_att_凸_1", mainPosi: { x: -14.41, y: -10.235 }, animSkin: '小贱', scalex: -0.25, scaley: 0.25 }, { id: 16, name: "Tetris_att_凸_2", mainPosi: { x: 0.131, y: 1.91 }, animSkin: '小贱', scalex: -0.25, scaley: 0.25 }, { id: 17, name: "Tetris_att_凸_3", mainPosi: { x: 15.166, y: -13.766 }, animSkin: '小贱', scalex: -0.25, scaley: 0.25 }, { id: 18, name: "Tetris_att_凸", mainPosi: { x: -0.001, y: -25.291 }, animSkin: '小贱', scalex: -0.25, scaley: 0.25 }, ] private _config: ITableBattleCube | null = null; _curentWeaponId: number = 0 weaponNode: Node = null; timeoutIndex: number = 0 animState: string; /**初始角色朝向*/ initRoleFacing: number = 0; initRoleFacingFix: number = 0; private roleNode: Node | null = null; private roleBaseAbsScaleX: number = 1; _isBattleScene = false //是否是战斗场景相机 _isCanShoot = true //是否可以射击 setConfig(config: ITableBattleCube, isCanShoot = true, isBattleScene = false) { this._config = config this._isBattleScene = isBattleScene this._isCanShoot = isCanShoot const cubeName = config.cubename; if (gg.game.CurentBattle.BattleType === BattleType.OrangeWeaponMode_Ya) { this._curentWeaponId = JIUJIUWOYA_INIT_WEAPON_ID; if (cubeName) { gg.game.CurentBattle.TetriWeaponMap.set(cubeName, JIUJIUWOYA_INIT_WEAPON_ID); } } else { const weaponId = gg.game.CurentBattle.TetriWeaponMap.get(cubeName); this._curentWeaponId = weaponId ?? config.weaponid ?? 0; } } getWeapontype(): number { return this._config?.weapontype ?? 0; } /** 救救我鸭:直接换武器(不走路径方块 / weapontype) */ applyWeaponId(weaponId: number, playReplaceEffect = false) { if (!Number.isFinite(weaponId) || weaponId <= 0) return; this._curentWeaponId = weaponId; const cubeName = this._config?.cubename; if (cubeName && gg.game?.CurentBattle) { gg.game.CurentBattle.TetriWeaponMap.set(cubeName, weaponId); } this.changeWeaponConf(); if (playReplaceEffect && gg.game?.CurentBattle?.BattleType === BattleType.OrangeWeaponMode_Ya) { this.showSkillSelectCompleteEffect(false, 0.3); } } protected onDestroy(): void { const block = this.findCarrierTetriNode()?.node ?? this.node; gg.game?.CurentBattle?.pruneDetachedBattleLayers?.(block); const weapon = this.weaponNode; const role = this.roleNode; this.weaponNode = null; this.roleNode = null; // 武器挂在「角色/武器」下,先回收武器再回收角色,避免子随父一起进半销毁态 if (this.timeoutIndex) clearTimeout(this.timeoutIndex as unknown as number); GEvent.Ins.off(GEvent.ChangeCubeWeaponConfig, this.ChangeCubeWeapon, this) GEvent.Ins.off(GEvent.StraightWeaponShoot, this.StraightWeaponShoot, this) GEvent.Ins.off(GEvent.StraightWeaponLaunch, this.StraightWeaponLaunch, this) GEvent.Ins.off(GEvent.StraightWeaponShootNum, this.StraightWeaponShootNum, this) GEvent.Ins.off(GEvent.StraightWeaponSplit, this.StraightWeaponSplit, this) GEvent.Ins.off(GEvent.StraightWeaponPierce, this.StraightWeaponPierce, this) GEvent.Ins.off(GEvent.ParabolaCollide, this.ParabolaCollide, this) GEvent.Ins.off(GEvent.ParabolaNoCollide, this.ParabolaNoCollide, this) GEvent.Ins.off(GEvent.SinglePointLaser, this.SinglePointLaser, this) GEvent.Ins.off(GEvent.StraightTargetDamage, this.StraightTargetDamage, this) GEvent.Ins.off(GEvent.LightningElectricity, this.LightningElectricity, this) this.safePutPoolNode(weapon); this.safePutPoolNode(role); } start() { //ChangeCubeWeaponConfig GEvent.Ins.on(GEvent.ChangeCubeWeaponConfig, this.ChangeCubeWeapon, this) //武器发射事件 GEvent.Ins.on(GEvent.StraightWeaponShoot, this.StraightWeaponShoot, this)//直线弹幕 GEvent.Ins.on(GEvent.StraightWeaponLaunch, this.StraightWeaponLaunch, this)//直线弹射 GEvent.Ins.on(GEvent.StraightWeaponShootNum, this.StraightWeaponShootNum, this)//直线数量 GEvent.Ins.on(GEvent.StraightWeaponSplit, this.StraightWeaponSplit, this) //直线分裂 GEvent.Ins.on(GEvent.StraightWeaponPierce, this.StraightWeaponPierce, this)//直线穿透 GEvent.Ins.on(GEvent.ParabolaCollide, this.ParabolaCollide, this)//抛物线碰撞 GEvent.Ins.on(GEvent.ParabolaNoCollide, this.ParabolaNoCollide, this)//抛物线没有碰撞 GEvent.Ins.on(GEvent.SinglePointLaser, this.SinglePointLaser, this)//单点激光 GEvent.Ins.on(GEvent.StraightTargetDamage, this.StraightTargetDamage, this)//直线目标持续伤害 GEvent.Ins.on(GEvent.LightningElectricity, this.LightningElectricity, this)//雷电电击攻击单体 if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Ya){ //已经有角色了直接修改皮肤 this.scheduleOnce(() => { //this.addWeapon() this.createRoleNode2() }, 0.02) }else{ this.scheduleOnce(() => { this.createRoleNode() }, 0.02) } } createRoleNode2(){ if (!this._config) { console.error('[tetriWeaponCarrier] createRoleNode: _config 为空,请保证 setConfig 在 start 之前已调用'); return; } this.roleNode = this.node const conf = this.roleConfig.find(item => item.name == this._config.cubename); // console.log('conf',conf); if (conf) { // this.roleNode.setPosition(conf.mainPosi.x, conf.mainPosi.y); this.roleNode.setScale(conf.scalex, conf.scaley); const anim = this.roleNode.getChildByName('anim')?.getComponent(sp.Skeleton); if (anim) { anim.setAnimation(0, '待机', true); anim.setSkin(conf.animSkin); } else { console.warn('[tetriWeaponCarrier] createRoleNode: 角色 prefab 缺少 anim 子节点', this._config.cubename); } } this.scheduleOnce(() => { if (!isValid(this.node)) return; this.setView(); }, 0.1); this.initRoleFacing = this.roleNode.scale.x; this.initRoleFacingFix = this.roleNode.scale.x; const abs = Math.abs(this.initRoleFacingFix); this.roleBaseAbsScaleX = abs > 1e-6 ? abs : 1; } createRoleNode() { // 先挂角色节点,再 setView -> addWeapon(依赖子路径 角色/anim、角色/武器) if (!this._config) { console.error('[tetriWeaponCarrier] createRoleNode: _config 为空,请保证 setConfig 在 start 之前已调用'); return; } // instantiate(整块) / 参考方块拷贝会带上已有「角色」;若再从池里取会叠两个 const dupDestroy: Node[] = []; let existingRole: Node | null = null; for (let i = 0; i < this.node.children.length; i++) { const c = this.node.children[i]; if (c.name === '角色') { if (!existingRole) existingRole = c; else dupDestroy.push(c); } } for (const d of dupDestroy) { if (d?.isValid) d.destroy(); } let roleNode: Node | null = existingRole?.isValid ? existingRole : null; if (!roleNode) { void gg.res.getNodeAsync('角色/', '角色', GBundle.tetriCards).then((loaded) => { if (!loaded?.isValid || !this.node?.isValid) return; this._setupRoleNode(loaded); }); return; } this._setupRoleNode(roleNode); } private _setupRoleNode(roleNode: Node) { roleNode.setParent(this.node); this.roleNode = roleNode; roleNode.active = true; const roleComp = roleNode.getComponent(role); roleComp?.setFollowTarget(this.findCarrierTetriNode()?.node ?? this.node); const conf = this.roleConfig.find(item => item.name == this._config.cubename); // console.log('conf',conf); if (conf) { roleNode.setPosition(conf.mainPosi.x, conf.mainPosi.y); roleNode.setScale(conf.scalex, conf.scaley); const anim = roleNode.getChildByName('anim')?.getComponent(sp.Skeleton); if (anim) { anim.setAnimation(0, '待机', true); anim.setSkin(conf.animSkin); } else { console.warn('[tetriWeaponCarrier] createRoleNode: 角色 prefab 缺少 anim 子节点', this._config.cubename); } } if (this._isBattleScene) { console.log('我是在战斗场景相机下创建角色', this.node.layer) this.setLayerMask(roleNode, this.node.layer) } this.scheduleOnce(() => { if (!isValid(this.node)) return; this.setView(); }, 0.1); this.initRoleFacing = this.roleNode.scale.x; this.initRoleFacingFix = this.roleNode.scale.x; const abs = Math.abs(this.initRoleFacingFix); this.roleBaseAbsScaleX = abs > 1e-6 ? abs : 1; } /**将“角色”翻转到面向目标 x(只改 scale.x,不改大小) */ private faceRoleToWorldX(targetWorldX: number) { if (!this.roleNode || !isValid(this.roleNode)) return; // 用载体方块作为参考坐标,更稳定(避免“角色”子节点有额外偏移) const selfX = this.node.worldPosition.x; const dx = targetWorldX - selfX; if (Math.abs(dx) < 0.01) return; // 你给的基准:初始一定是“脸朝右”,但 scale.x 可能正/负 // 所以把初始符号视为“朝右符号”,朝左时取反即可 const rightSign = this.initRoleFacingFix >= 0 ? 1 : -1; const dir = dx >= 0 ? rightSign : -rightSign; const s = this.roleNode.scale; const newX = this.roleBaseAbsScaleX * dir; if (Math.abs(s.x - newX) > 1e-6) { this.roleNode.setScale(newX, s.y, s.z); } } private faceRoleToMonster(monsterNode: Node | null) { if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Ya){ return; } if (!monsterNode || !isValid(monsterNode)) return; this.faceRoleToWorldX(monsterNode.worldPosition.x); } /** * 回收到 gg.res 节点池。父节点被 destroy 时,子节点可能已在销毁流程中, * 必须先脱父且 isValid 再 putNode,否则 putNode 里 getComponentsInChildren 会空引用。 */ private safePutPoolNode(n: Node | null): void { if (!n) return; if (!isValid(n)) return; try { if (n.parent) n.destroy(); //gg.res.putNode(n); } catch (e) { console.warn('[tetriWeaponCarrier] safePutPoolNode failed', e); } } setView() { const conf = gg.data.project.getWeaponData(this._curentWeaponId); if (!conf) { console.error('[tetriWeaponCarrier] setView: 武器表无数据 weaponId=', this._curentWeaponId, this._config?.cubename); return; } this.addWeapon(conf.weaponani); //this.changeIcon(conf.typeicon); } ChangeCubeWeapon(cubename: string, weaponid: number) { if (gg.game?.CurentBattle?.BattleType === BattleType.OrangeWeaponMode_Ya) { return; } if (cubename == this._config?.cubename) { this._curentWeaponId = weaponid; this.changeWeaponConf(); } } /** * 三选一完成格子效果(救救我鸭无方块时挂在角色载体上;与 tetriNode.showUpLevelEffect 一致) * @param isUpLevel true 技能升级,false 武器替换 */ showSkillSelectCompleteEffect(isUpLevel = true, delay = 0) { const host = this.roleNode?.isValid ? this.roleNode : this.node; if (!host?.isValid) return; this.scheduleOnce(() => { if (!host?.isValid) return; void gg.res.getNodeAsync(GPath.Prefab, '三选一完成格子效果', GBundle.tetriGame).then((eff) => { if (!eff?.isValid) return; const battle = gg.game?.CurentBattle; const ya = battle?.BattleType === BattleType.OrangeWeaponMode_Ya; const spineLayer = ya && battle?.TopSpineLayer?.isValid ? battle.TopSpineLayer : null; if (spineLayer) { eff.parent = spineLayer; eff.setWorldPosition(host.worldPosition); battle.setLayerMask(eff, spineLayer.layer); eff.setSiblingIndex(spineLayer.children.length - 1); } else { gg.ui.showNode(eff, UILayer.Anim); eff.parent = host; eff.setPosition(Vec3.ZERO); } eff.active = true; const nReplace = eff.find('武器替换'); const nUp = eff.find('升级'); if (nReplace?.isValid) nReplace.active = !isUpLevel; if (nUp?.isValid) nUp.active = isUpLevel; const playSpine = (node: Node | null, animName: string) => { const sk = node?.getComponent(sp.Skeleton); if (!sk) return; sk.clearTracks(); sk.setAnimation(0, animName, false); }; if (nReplace?.active) playSpine(nReplace, 'animation'); if (nUp?.active) playSpine(nUp, '升级'); this.scheduleOnce(() => { if (eff?.isValid) gg.res.putNode(eff); }, 1.5); }); }, delay); } changeWeaponConf() { console.log('我改变武器啦 ++= changeWeaponConf++++++++===', this._curentWeaponId) let conf2 = gg.data.project.getWeaponData(this._curentWeaponId) //this.changeIcon(conf2.typeicon) this.changeWeapon(conf2.weaponani) } changeWeapon(weaponani) { const old = this.weaponNode; this.weaponNode = null; this.safePutPoolNode(old); this.addWeapon(weaponani) } //增加武器和修改icon图片 addWeapon(weaponani) { const anim = find('anim', this.roleNode); const wuqiNode = find('武器', this.roleNode); if (!anim || !wuqiNode) { console.error('[tetriWeaponCarrier] addWeapon: 未找到 角色/anim 或 角色/武器', this._config?.cubename); return; } void gg.res.getNodeAsync('prefab/', weaponani, GBundle.tetriWeapon).then((weaponNode) => { if (!weaponNode?.isValid || !wuqiNode?.isValid) return; this.setLayerMask(weaponNode, wuqiNode.layer); this.weaponNode = weaponNode; weaponNode.setParent(wuqiNode); anim.getComponent(sp.Skeleton)?.setAnimation(0, '待机', true); weaponNode.getComponent(sp.Skeleton)?.setAnimation(0, '待机', true); }); } setLayerMask(n: Node, layerMask: number) { n.layer = layerMask; for (let i = 0; i < n.children.length; i++) { if (n.children[i].isValid) { this.setLayerMask(n.children[i], layerMask); } } } changeIcon(typeicon) { } //播放攻击动画 playAtkAnim() { clearTimeout(this.timeoutIndex as unknown as number); if (!this.roleNode?.isValid) { return; } const animNode = find('anim', this.roleNode); const animSk = animNode?.getComponent(sp.Skeleton); const weaponSk = this.weaponNode?.getComponent(sp.Skeleton); if (animSk) { animSk.setAnimation(0, '攻击', false); } if (weaponSk) { weaponSk.setAnimation(0, '攻击', false); } this.timeoutIndex = setTimeout(() => { if (animSk?.isValid) { animSk.setAnimation(0, '待机', true); } if (weaponSk?.isValid) { weaponSk.setAnimation(0, '待机', true); } this.animState = '待机'; this.timeoutIndex = null; }, 1000) as unknown as number; } //获取武器的发射节点和原点 getWeaponLaunchNode(): Node { if (!this.roleNode?.isValid) { return this.node; } const wuqiNode = find('武器', this.roleNode); if (!wuqiNode?.isValid) { return this.roleNode; } const shoot = wuqiNode.getComponentInChildren(shootPoint)?.node; if (shoot?.isValid) { return shoot; } // 部分武器预制未挂 shootPoint:用武器模型节点(只要在「武器」子树下)或「武器」挂点作为发射参考 if (this.weaponNode?.isValid) { let p: Node | null = this.weaponNode; while (p) { if (p === wuqiNode) { return this.weaponNode; } p = p.parent; } } return wuqiNode; } /** 载体关联方块:自身或父链上的 tetriNode(救救我鸭等直挂角色时为 null) */ findCarrierTetriNode(): tetriNode | null { const onSelf = this.node.getComponent(tetriNode); if (onSelf) return onSelf; let cur: Node | null = this.node.parent; while (cur) { const t = cur.getComponent(tetriNode); if (t) return t; cur = cur.parent; } return null; } /** 方块未落稳不发射;无方块载体(副本直挂等)视为可随时发射 */ isSettledForWeaponShoot(): boolean { const tetri = this.findCarrierTetriNode(); return !tetri || tetri.isSettled; } getCarrierTetriLevel(): number { return this.findCarrierTetriNode()?.getNodeLevel?.() ?? 1; } /** 战斗暂停/弹窗叠层时不发射已排队的子弹 */ private scheduleWeaponFire(delaySec: number, fn: () => void): void { this.scheduleOnce(() => { if (!gg.game?.CurentBattle?.canUpdateFrame()) return; fn(); }, delaySec); } /** GEvent 武器发射为全局广播,仅处理与当前发射塔绑定的武器 */ private shouldHandleWeaponLaunch(emitWeaponId: number): boolean { return emitWeaponId > 0 && emitWeaponId === this._curentWeaponId; } /**直线弹幕 */ StraightWeaponShoot(weaponProperty: IWeaponProperty, posX, xTime, launchCount, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } //没有落稳的方块不在发射武器 //单次发射数量launchCount // console.log('StraightWeaponShoot 单次发射数量launchCount',launchCount) // console.log('StraightWeaponShoot burstCount',burstCount) if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线弹幕) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponShoot, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); // 音效节流交给 SoundManager.shouldPlayWeaponSfx(按音效名);勿共用 audio_timeStamp this.playEffectStraight(weaponId); for (let j = 0; j < launchCount; j++) { let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线弹射 */ StraightWeaponLaunch(weaponProperty: IWeaponProperty, posX, xTime, launchCount, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线弹射) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponLaunch, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); this.playEffectStraightLaunch(weaponId); for (let j = 0; j < launchCount; j++) { let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线数量 */ StraightWeaponShootNum(weaponProperty: IWeaponProperty, burstCount, xTime, launchCount, posX) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线数量) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponShootNum, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); this.playEffectStraightNum(weaponId); for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线分裂 */ StraightWeaponSplit(weaponProperty: IWeaponProperty, burstCount, xTime, launchCount, posX) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线分裂) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponSplit, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); this.playEffectStraight(weaponId); for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线穿透*/ StraightWeaponPierce(weaponProperty: IWeaponProperty, burstCount, xTime, launchCount, posX) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线穿透) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponPierce, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC2 / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); this.playEffectStraight(weaponId); for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**抛物线碰撞 */ ParabolaCollide(weaponProperty: IWeaponProperty, bullet_column, launchCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let throwCount = weaponProperty.throwCount let specialSkillInfo = weaponProperty.specialSkillInfo let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.抛物线碰撞) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.ParabolaCollide, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; let createSkill = async (mNode: Node) => { if (mNode && isValid(mNode)) { // weaponNode 挂在 SkillController.node 下,setData 的 targetPos 需要与 weaponNode 同坐标系 const parentUi = battle.SkillController.node.getComponent(UITransform); let pos = parentUi ? parentUi.convertToNodeSpaceAR(mNode.worldPosition) : mNode.worldPosition.clone(); void battle.SkillController.instantiateSingleWeapon(weaponId, volume, v3(0, 0, 0), 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), pos, false, false, 0, 0, 0, 0, tetriLevel); }); } } let singleLaunchCall = () => { //单次发射数量 this.playEffectParabolaCollide(weaponId); let selectMonster = []; selectMonster = gg.game.CurentBattle.MonsterSpawner.getMonsterNearestCountPar(this.node, throwCount); if (selectMonster.length == 0) { return } // 面向第一个目标怪物 this.faceRoleToMonster(selectMonster[0]); for (let i = 0; i < throwCount; i++) { if (i >= throwCount) break; if (i >= selectMonster.length) break; let m = selectMonster[i]; if (!isValid(m)) continue; createSkill(m); } } let isBurst = false let burstCount = 1 let xTime = bullet_column//间隔 for (let i = 0; i < specialSkillInfo.length; i++) { let buffInfo = specialSkillInfo[i] let arr = buffInfo.split(',') let buffType = Number(arr[0]) if (buffType == SpecialSkillType.rateBurst) { //概率连发 let rateNum = Number(arr[1]) / 10000//连发的概率 burstCount += Number(arr[2])//增加连发数量 isBurst = Math.random() < rateNum } } //连发 if (isBurst) { for (let j = 0; j < burstCount; j++) { this.scheduleWeaponFire(lead + xTime * j, () => { singleLaunchCall(); }); } //单发 } else { this.scheduleWeaponFire(lead, () => { // 多发:一次性取足目标,避免每次 random 到同一个怪 const totalNeed = Math.max(1, Math.floor(throwCount)) * Math.max(1, Math.floor(launchCount)); const selectMonsterAll = battle.MonsterSpawner.getMonsterParabolaPos(totalNeed, carrierNode); if (selectMonsterAll.length == 0) return; // 面向第一个目标怪物 const first = selectMonsterAll[0]?.node as Node; if (first && isValid(first)) { this.faceRoleToMonster(first); } for (let k = 0; k < totalNeed; k++) { if (k >= selectMonsterAll.length) break; const m = selectMonsterAll[k]; if (!m?.node || !isValid(m.node)) continue; createSkill(m.node); } }); } } }); } } /**抛物线没有碰撞 */ ParabolaNoCollide(weaponProperty: IWeaponProperty, bullet_column, launchCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let throwCount = weaponProperty.throwCount let specialSkillInfo = weaponProperty.specialSkillInfo let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.抛物线没有碰撞) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.ParabolaNoCollide, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; let createSkill = async (mNode: Node) => { if (mNode && isValid(mNode)) { // weaponNode 挂在 SkillController.node 下,setData 的 targetPos 需要与 weaponNode 同坐标系 const parentUi = battle.SkillController.node.getComponent(UITransform); let pos = parentUi ? parentUi.convertToNodeSpaceAR(mNode.worldPosition) : mNode.worldPosition.clone(); void battle.SkillController.instantiateSingleWeapon(weaponId, volume, v3(0, 0, 0), 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), pos, false, false, 0, 0, 0, 0, tetriLevel); }); } } let singleLaunchCall = () => { //单次发射数量 this.playEffectParabolaNoCollide(weaponId); let selectMonster = []; selectMonster = gg.game.CurentBattle.MonsterSpawner.getMonsterParabolaPos(throwCount, this.node); if (selectMonster.length == 0) { return } // 大炮等“抛物线没有碰撞”类型也需要左右转向(与其它发射类型保持一致) const first = selectMonster[0]?.node as Node; if (first && isValid(first)) { this.faceRoleToMonster(first); } for (let i = 0; i < throwCount; i++) { if (i >= throwCount) break; if (i >= selectMonster.length) break; let m = selectMonster[i]; if (!isValid(m.node)) continue; createSkill(m.node); } } let isBurst = false let burstCount = 1 let xTime = bullet_column//间隔 for (let i = 0; i < specialSkillInfo.length; i++) { let buffInfo = specialSkillInfo[i] let arr = buffInfo.split(',') let buffType = Number(arr[0]) if (buffType == SpecialSkillType.rateBurst) { //概率连发 let rateNum = Number(arr[1]) / 10000//连发的概率 burstCount += Number(arr[2])//增加连发数量 isBurst = Math.random() < rateNum } } //连发 if (isBurst) { for (let j = 0; j < burstCount; j++) { this.scheduleWeaponFire(lead + xTime * j, () => { singleLaunchCall(); }); } //单发 } else { this.scheduleWeaponFire(lead, () => { // 多发:一次性取足目标,避免每次 random 到同一个怪 const totalNeed = Math.max(1, Math.floor(throwCount)) * Math.max(1, Math.floor(launchCount)); const selectMonsterAll = battle.MonsterSpawner.getMonsterParabolaPos(totalNeed, carrierNode); if (selectMonsterAll.length == 0) return; // 面向第一个目标怪物 const first = selectMonsterAll[0]?.node as Node; if (first && isValid(first)) { this.faceRoleToMonster(first); } for (let k = 0; k < totalNeed; k++) { if (k >= selectMonsterAll.length) break; const m = selectMonsterAll[k]; if (!m?.node || !isValid(m.node)) continue; createSkill(m.node); } }); } } }); } } /**单点激光 */ SinglePointLaser(weaponProperty: IWeaponProperty, burstCount, launchCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.单点激光) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.SinglePointLaser, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC2 / battle.TimeScale; this.scheduleWeaponFire(lead, () => { for (let i = 0; i < burstCount; i++) { for (let j = 0; j < launchCount; j++) { const laserIndex = i * launchCount + j; if (i == 0 && j == 0) { gg.audio.playEffect({ name: '激光', path: 'sound/武器音效2/' }); } // void battle.SkillController.instantiateSingleWeapon(weaponId, volume, v3(0, 0, 0), 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; const skillComp = weaponNode.getComponent(Skill); skillComp.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, laserIndex, 0, 0, 0, tetriLevel); weaponNode.getComponent(LaserLine).setData(weaponId, weaponProperty, laserIndex, tetriLevel); }); } } }); } }); } } /**直线目标持续伤害 */ StraightTargetDamage(weaponProperty: IWeaponProperty, launchCount, xTime, posX, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线目标持续伤害) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightTargetDamage, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); // 音效节流交给 SoundManager.shouldPlayWeaponSfx(按音效名);勿共用 audio_timeStamp this.playEffectStraight(weaponId); for (let j = 0; j < launchCount; j++) { let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } LightningElectricity(weaponProperty: IWeaponProperty, posX, xTime, launchCount, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } //雷电武器 if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.无弹道单体) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.LightningElectricity, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); this.playEffectStraightNum(weaponId); for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线播放武器音效 */ playEffectStraight(weaponId: number) { //1,9,14,16 if (weaponId == WeaponType.DanGong) { gg.audio.playEffect({ name: '弹弓', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.TuJiBuBuQiang) { gg.audio.playEffect({ name: '突击步枪', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.DianCiPao) { gg.audio.playEffect({ name: '电磁炮', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ZhanShuZhuaDao) { gg.audio.playEffect({ name: '战术爪刀', path: 'sound/武器音效2/' }); }else if (weaponId == WeaponType.LiSanDaoDan) { gg.audio.playEffect({ name: '导弹', path: 'sound/武器音效2/' }); }else if (weaponId == WeaponType.LeiShenZhiChui) { gg.audio.playEffect({ name: '雷击', path: 'sound/武器音效2/' }); } } /**直线数量播放武器音效 */ playEffectStraightNum(weaponId: number) { //3 if (weaponId == WeaponType.ChangGong) { gg.audio.playEffect({ name: '射箭', path: 'sound/武器音效2/' }); }else if (weaponId == WeaponType.LeiShenZhiChui) { gg.audio.playEffect({ name: '雷击', path: 'sound/武器音效2/' }); } } /**直线弹射播放武器音效 */ playEffectStraightLaunch(weaponId: number) { //5,7 if (weaponId == WeaponType.HuiXuanBiao) { gg.audio.playEffect({ name: '回旋镖', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.FaZhen) { gg.audio.playEffect({ name: '法杖', path: 'sound/武器音效2/' }); } } /**抛物线碰撞播放武器音效 */ playEffectParabolaCollide(weaponId: number) { //2,4,6 if (weaponId == WeaponType.BiaoQiang) { gg.audio.playEffect({ name: '长矛', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ShiFu) { gg.audio.playEffect({ name: '斧头', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.FeiJian) { gg.audio.playEffect({ name: '飞剑', path: 'sound/武器音效2/' }); } } /**抛物线没有碰撞播放武器音效 */ playEffectParabolaNoCollide(weaponId: number) { //8,10,11,13,15 if (weaponId == WeaponType.FuWenTa) { gg.audio.playEffect({ name: '符文塔', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.DaPao) { gg.audio.playEffect({ name: '大炮', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ShouLiuDan) { gg.audio.playEffect({ name: '手榴弹', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ZhuiZongDaoDan) { gg.audio.playEffect({ name: '追踪导弹', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.BingDongShouLei) { gg.audio.playEffect({ name: '冰冻手雷', path: 'sound/武器音效2/' }); } } getCurWeaponId() { return this._curentWeaponId } setIsShootCan(can: boolean) { this._isCanShoot = can } }