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.
718 lines
28 KiB
718 lines
28 KiB
|
1 week ago
|
import { _decorator, CCBoolean, Component, find, instantiate, Label, Node, NodePool, Prefab, sp, tween, UITransform, v3 } from 'cc';
|
||
|
|
import { UIBase } from 'db://assets/mx/module/ui/UIBase';
|
||
|
|
import { GBundle, UIID } from '../../game/ConfigRes';
|
||
|
|
import { zidanScript } from './zidanScript';
|
||
|
|
import { monsterScript } from './monsterScript';
|
||
|
|
import { GEvent } from 'db://assets/mx/module/event/GEvent';
|
||
|
|
import { atgSkill } from './atgSkill';
|
||
|
|
import MTools from 'db://assets/mx/tools/MTools';
|
||
|
|
import { atgDoor } from './atgDoor';
|
||
|
|
const { ccclass, property } = _decorator;
|
||
|
|
|
||
|
|
@ccclass('gaoyaguo')
|
||
|
|
export class gaoyaguo extends Component {
|
||
|
|
|
||
|
|
|
||
|
|
moveDoor: Node = null;
|
||
|
|
menMove: Node = null;
|
||
|
|
Gaozidan: Node = null;
|
||
|
|
gaoyaguoNode: Node = null;
|
||
|
|
gaoyaguo_guo: Node = null;
|
||
|
|
gaoyaguozidanLayer: Node = null;
|
||
|
|
guoEffect: Node = null;
|
||
|
|
guoEffectFire: Node = null;
|
||
|
|
ui_center: Node = null;
|
||
|
|
monsterCore: monsterScript = null;
|
||
|
|
zidanCore: zidanScript = null;
|
||
|
|
monsterLayer: Node = null;
|
||
|
|
gaoyaguoTime: number = 0;
|
||
|
|
gaoyaguospeend = 0.5
|
||
|
|
gaoyaguoMaxTime = 0.03
|
||
|
|
zhujueNode: Node = null;
|
||
|
|
qiangNode1: Node = null;
|
||
|
|
qiangNode2: Node = null;
|
||
|
|
quedanNode: Node = null;
|
||
|
|
zhuzhutaiNode: Node = null;
|
||
|
|
zhujueSpine: Node = null
|
||
|
|
zhujueSpineAnimation: string = '待机'
|
||
|
|
isPlayAnim = false
|
||
|
|
jiedianIndex = 1
|
||
|
|
public static Instance: gaoyaguo = null;
|
||
|
|
|
||
|
|
zhujueTakeZidanNum = 0
|
||
|
|
_sGame: boolean = false;
|
||
|
|
|
||
|
|
isGameOver = false
|
||
|
|
sendZidanTimeFix = 0.1
|
||
|
|
sendZidanTime = 0
|
||
|
|
skillMonsterNum = 0
|
||
|
|
|
||
|
|
gaoyaguoHieNum = 666
|
||
|
|
@property(CCBoolean)
|
||
|
|
debugSendZidan: boolean = false;
|
||
|
|
guoNum = 0
|
||
|
|
onDestroy(): void {
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
onLoad(): void {
|
||
|
|
gaoyaguo.Instance = this;
|
||
|
|
this.ui_center = this.node.getChildByName('ui_center');
|
||
|
|
this.menMove = find('menMove', this.ui_center);
|
||
|
|
this.zhujueNode = find('zhandou/主角', this.ui_center);
|
||
|
|
this.qiangNode1 = find('zhandou/主角墙1', this.ui_center);
|
||
|
|
this.qiangNode2 = find('zhandou/主角墙2', this.ui_center);
|
||
|
|
this.quedanNode = find('zhandou/缺弹提示', this.ui_center);
|
||
|
|
this.zhujueSpine = find('zhandou/主角/主角spine', this.ui_center);
|
||
|
|
|
||
|
|
this.zhuzhutaiNode = find('zhandou/求助字体', this.ui_center);
|
||
|
|
this.gaoyaguoNode = this.ui_center.getChildByName('高压锅');
|
||
|
|
this.gaoyaguo_guo = this.gaoyaguoNode.getChildByName('锅');
|
||
|
|
this.gaoyaguozidanLayer = this.gaoyaguoNode.getChildByName('子弹层');
|
||
|
|
this.monsterCore = this.ui_center.getChildByName('monsterCore').getComponent(monsterScript);
|
||
|
|
this.zidanCore = this.ui_center.getChildByName('zidanCore').getComponent(zidanScript);
|
||
|
|
this.guoEffect = this.gaoyaguo_guo.getChildByName('攻击效果');
|
||
|
|
this.guoEffectFire = this.gaoyaguo_guo.getChildByName('喷火效果');
|
||
|
|
this.Gaozidan = this.ui_center.getChildByName('高压锅子弹');
|
||
|
|
this.guoNum = this.gaoyaguoHieNum//parseInt(this.gaoyaguo_guo.getChildByName('num').getComponent(Label).string);
|
||
|
|
this.gaoyaguo_guo.getChildByName('num').getComponent(Label).string = this.guoNum.toString()
|
||
|
|
this.moveDoor = find('光门蓝5Move', this.menMove);
|
||
|
|
|
||
|
|
|
||
|
|
let bg2 = this.ui_center.getChildByName('bg2')
|
||
|
|
this.monsterLayer = bg2.getChildByName('monsterLayer');
|
||
|
|
//开始游戏
|
||
|
|
this.gaoyaguoNode.active = true
|
||
|
|
|
||
|
|
this.ui_center.getChildByName('引导指针').active = true
|
||
|
|
this.startGame()
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
protected onEnable(): void {
|
||
|
|
GEvent.Ins.on(GEvent.gaoyaguoAttack, this.gaoyaguoAttack, this);
|
||
|
|
GEvent.Ins.on(GEvent.gaoyaguoAttackToPlayer, this.gaoyaguoAttackToPlayer, this);
|
||
|
|
GEvent.Ins.on(GEvent.hitGaoyaguo, this.hitGaoyaguo, this);
|
||
|
|
GEvent.Ins.on(GEvent.hitZhujue, this.hitZhujue, this);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected onDisable(): void {
|
||
|
|
GEvent.Ins.off(GEvent.gaoyaguoAttack, this.gaoyaguoAttack, this);
|
||
|
|
GEvent.Ins.off(GEvent.gaoyaguoAttackToPlayer, this.gaoyaguoAttackToPlayer, this);
|
||
|
|
GEvent.Ins.off(GEvent.hitGaoyaguo, this.hitGaoyaguo, this);
|
||
|
|
GEvent.Ins.off(GEvent.hitZhujue, this.hitZhujue, this);
|
||
|
|
}
|
||
|
|
|
||
|
|
hitZhujue(hitCountOrZidan: number, zidanMaybe?: Node) {
|
||
|
|
//子弹数目给主角(每颗子弹单独事件 +1,避免与主角/墙盒多帧重叠时重复累加)
|
||
|
|
const add = typeof hitCountOrZidan === 'number' ? hitCountOrZidan : 1;
|
||
|
|
this.zhujueTakeZidanNum += add;
|
||
|
|
//console.log('hitZhujue++++ this.zhujueTakeZidanNum +++===', add, this.zhujueTakeZidanNum )
|
||
|
|
this.zhujueNode.getChildByName('zidanNum').active = true
|
||
|
|
let zidanNum = this.zhujueNode.getChildByName('zidanNum').getComponent(Label)
|
||
|
|
zidanNum.string = this.zhujueTakeZidanNum.toString()
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
hitGaoyaguo(hitCountOrZidan: number | Node, zidanMaybe?: Node) {
|
||
|
|
const hitCount = typeof hitCountOrZidan === 'number' ? hitCountOrZidan : 1;
|
||
|
|
this.guoNum -= hitCount;
|
||
|
|
this.gaoyaguo_guo.getChildByName('num').getComponent(Label).string = this.guoNum.toString();
|
||
|
|
|
||
|
|
//从高压锅上掉落子弹,子弹角度随机 ,掉落的位置子弹层的子节点上随机。
|
||
|
|
//子节点如果上面的子弹如果大于20,就判断掉落的子弹和现有子弹的位置有没有重叠,子弹掉落完成,删除重叠的子弹
|
||
|
|
for (let i = 0; i < hitCount; i++) {
|
||
|
|
this.dropZidan()
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!MTools.beforeTimes(110, "hitGaoyaguo")) {
|
||
|
|
|
||
|
|
tween(this.gaoyaguo_guo)
|
||
|
|
.to(0.1, { scale: v3(1.1, 1.1, 1.1) })
|
||
|
|
.call(() => {
|
||
|
|
this.gaoyaguo_guo.scale = v3(1, 1, 1)
|
||
|
|
})
|
||
|
|
.start()
|
||
|
|
|
||
|
|
this.guoEffect.active = true
|
||
|
|
this.guoEffect.scale = v3(0.1, 0.1, 0.1)
|
||
|
|
tween(this.guoEffect)
|
||
|
|
.to(0.1, { scale: v3(1, 1, 1) })
|
||
|
|
.call(() => {
|
||
|
|
this.guoEffect.active = false
|
||
|
|
})
|
||
|
|
.start()
|
||
|
|
|
||
|
|
|
||
|
|
this.guoEffectFire.active = true
|
||
|
|
this.guoEffectFire.scale = v3(0.1, 0.1, 0.1)
|
||
|
|
tween(this.guoEffectFire)
|
||
|
|
.to(0.1, { scale: v3(1, 1, 1) })
|
||
|
|
.call(() => {
|
||
|
|
this.guoEffectFire.active = false
|
||
|
|
})
|
||
|
|
.start()
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if (this.guoNum <= 0) {
|
||
|
|
|
||
|
|
this.gaoyaguoNode.getChildByName('光门1').active = false
|
||
|
|
this.gaoyaguoNode.getChildByName('光门2').active = false
|
||
|
|
this.gaoyaguoNode.getChildByName('墙4').active = false
|
||
|
|
this.gaoyaguoNode.getChildByName('墙').active = false
|
||
|
|
this.gaoyaguoNode.getChildByName('墙2').active = false
|
||
|
|
this.gaoyaguoNode.getChildByName('墙3').active = false
|
||
|
|
let baozha = this.gaoyaguoNode.getChildByName('爆炸4')
|
||
|
|
this.gaoyaguoNode.getChildByName('锅').active = false
|
||
|
|
baozha.active = true
|
||
|
|
gg.audio.playEffect({ name: '汽水炸弹爆炸', path: 'sound/武器音效/' });
|
||
|
|
//停止发射子弹
|
||
|
|
this._sGame = false
|
||
|
|
// this.gaoyaguozidanLayer 移除
|
||
|
|
for (let i = 0; i < this.gaoyaguozidanLayer.children.length; i++) {
|
||
|
|
let node = this.gaoyaguozidanLayer.children[i]
|
||
|
|
node.destroyAllChildren()
|
||
|
|
}
|
||
|
|
GEvent.Ins.emit(GEvent.gaoyaguoBoom, this.jiedianIndex)
|
||
|
|
this.scheduleOnce(() => {
|
||
|
|
baozha.active = false
|
||
|
|
|
||
|
|
//进入下一个阶段
|
||
|
|
this.gaoyaguozidanLayer.active = false
|
||
|
|
|
||
|
|
//场景向下移动
|
||
|
|
this.gaoyaguoNode.active = false
|
||
|
|
this.beginMoveScene()
|
||
|
|
|
||
|
|
}, 0.5)
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
dropZidan() {
|
||
|
|
let zidan = instantiate(this.Gaozidan)
|
||
|
|
let findNode = null
|
||
|
|
let allAdd20 = false
|
||
|
|
for (let i = 0; i < this.gaoyaguozidanLayer.children.length; i++) {
|
||
|
|
let node = this.gaoyaguozidanLayer.children[i]
|
||
|
|
if (node.children.length < 30) {
|
||
|
|
findNode = node
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!findNode) {
|
||
|
|
let randomIndex = Math.floor(Math.random() * this.gaoyaguozidanLayer.children.length)
|
||
|
|
findNode = this.gaoyaguozidanLayer.children[randomIndex]
|
||
|
|
allAdd20 = true
|
||
|
|
}
|
||
|
|
|
||
|
|
findNode.addChild(zidan)
|
||
|
|
let zidanAngle = Math.random() * 360
|
||
|
|
zidan.worldPosition = this.guoEffect.worldPosition
|
||
|
|
let endPosix = findNode.getComponent(UITransform).contentSize.width / 2
|
||
|
|
let endPosiy = findNode.getComponent(UITransform).contentSize.height / 2
|
||
|
|
//-endPosix dao endPosix
|
||
|
|
let randx = MTools.getRandomValue(-endPosix, endPosix)
|
||
|
|
let randy = MTools.getRandomValue(-endPosiy, endPosiy)
|
||
|
|
zidan.active = true
|
||
|
|
tween(zidan)
|
||
|
|
.to(0.5, { x: randx, y: randy, angle: zidanAngle })
|
||
|
|
.call(() => {
|
||
|
|
zidan.active = true
|
||
|
|
//检测有没有重叠的子弹
|
||
|
|
if (allAdd20) {
|
||
|
|
this.checkZidanOverlap(findNode, zidan)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.start()
|
||
|
|
}
|
||
|
|
|
||
|
|
checkZidanOverlap(findNode: Node, zidan: Node) {
|
||
|
|
let zidanList = findNode.children
|
||
|
|
for (let i = 0; i < zidanList.length; i++) {
|
||
|
|
let zidanItem = zidanList[i]
|
||
|
|
if (zidanItem.worldPosition.equals(zidan.worldPosition)) {
|
||
|
|
zidanItem.active = false
|
||
|
|
zidanItem.destroy()
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private _worldDirToZidanCoreLocalDir(zidanCoreNode: Node, startWorld: any, dirWorld: any) {
|
||
|
|
const parentTrans = zidanCoreNode.getComponent(UITransform);
|
||
|
|
if (!parentTrans) {
|
||
|
|
return dirWorld;
|
||
|
|
}
|
||
|
|
// 将 world 方向转换到 zidanCoreNode 的本地坐标系里(zidanScript.update 用的是 child.position/local 坐标移动)
|
||
|
|
const startLocal = parentTrans.convertToNodeSpaceAR(startWorld);
|
||
|
|
const endLocal = parentTrans.convertToNodeSpaceAR(startWorld.clone().add(dirWorld));
|
||
|
|
const dirLocal = endLocal.subtract(startLocal);
|
||
|
|
dirLocal.z = 0;
|
||
|
|
if (dirLocal.lengthSqr() < 0.0001) {
|
||
|
|
return v3(0, 1, 0);
|
||
|
|
}
|
||
|
|
dirLocal.normalize();
|
||
|
|
return dirLocal;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
gaoyaguoAttackToPlayer(zidanNum: number, startNode: Node, zidanTakeNumber) {
|
||
|
|
// 创建子弹,子弹方向(门:0=向上,1=朝主角)
|
||
|
|
let zidanNum2 = 1
|
||
|
|
if (zidanNum == 30) {
|
||
|
|
zidanNum2 = 10
|
||
|
|
}
|
||
|
|
if (zidanNum == 20) {
|
||
|
|
zidanNum2 = 100
|
||
|
|
}
|
||
|
|
if (zidanNum == 30 || zidanNum == 20) {
|
||
|
|
zidanNum = zidanNum / 10
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const door = startNode.getComponent(atgDoor);
|
||
|
|
const direction = door?.direction ?? 0;
|
||
|
|
const sourceDoorNumber = door?.doorNumber ?? zidanNum;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
const zidanCoreNode = this.ui_center.getChildByName('zidanCore');
|
||
|
|
if (!zidanCoreNode) return;
|
||
|
|
|
||
|
|
// 子弹在出生点做横向“平铺”偏移,避免完全重叠
|
||
|
|
const spacing = 36; // 世界坐标间距(按子弹尺寸可调)
|
||
|
|
// 注意:x30/x20 会把 zidanNum 变成 3/2,这里必须用“修正后的 zidanNum”算中心,否则子弹出生点会被偏移到屏幕外
|
||
|
|
const centerIndex = (zidanNum - 1) / 2;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
//最多暂时6个子弹
|
||
|
|
// 主角世界坐标(勿在循环内用 subtract 改写,Vec3.subtract 会 mutate 调用者)
|
||
|
|
const protagonistWorld =
|
||
|
|
direction === 0
|
||
|
|
? null
|
||
|
|
: (this.zhujueNode?.worldPosition?.clone() ?? null);
|
||
|
|
|
||
|
|
|
||
|
|
//gg.audio.playEffect({ name: '彩虹飞弹发射', path: 'sound/武器音效/' });
|
||
|
|
for (let i = 0; i < zidanNum; i++) {
|
||
|
|
const zidan = this.zidanCore.getZidan();
|
||
|
|
|
||
|
|
zidan.scale = v3(1, 1, 1);
|
||
|
|
const startWorld = startNode.worldPosition.clone();
|
||
|
|
startWorld.x += (i - centerIndex) * spacing;
|
||
|
|
zidan.active = false
|
||
|
|
zidanCoreNode.addChild(zidan);
|
||
|
|
// 先挂到正确父节点,再设置 worldPosition,避免 reparent 导致世界坐标被改写
|
||
|
|
zidan.worldPosition = startWorld;
|
||
|
|
|
||
|
|
const skill = zidan.getComponent(atgSkill);
|
||
|
|
|
||
|
|
skill.setSkillData(this.zidanCore, zidanNum2);
|
||
|
|
skill.owner = 'enemy';
|
||
|
|
// 这批子弹由某个门生成,短时间内忽略与该门的碰撞,防止递归触发
|
||
|
|
// x5 等高倍率门更容易“出生点仍在门碰撞盒内”导致指数递归,忽略帧数加大
|
||
|
|
skill.setIgnoreDoor(startNode, sourceDoorNumber);
|
||
|
|
this.scheduleOnce(() => {
|
||
|
|
zidan.active = true
|
||
|
|
}, 0.1)
|
||
|
|
// zidanScript.update 用的是 zidanCoreNode 的本地坐标移动,所以这里直接计算“本地方向”
|
||
|
|
let dirLocal = v3(0, 1, 0);
|
||
|
|
if (protagonistWorld) {
|
||
|
|
const parentTrans = zidanCoreNode.getComponent(UITransform);
|
||
|
|
if (parentTrans) {
|
||
|
|
const startLocal = parentTrans.convertToNodeSpaceAR(startWorld);
|
||
|
|
const targetLocal = parentTrans.convertToNodeSpaceAR(protagonistWorld);
|
||
|
|
dirLocal = targetLocal.subtract(startLocal);
|
||
|
|
dirLocal.z = 0;
|
||
|
|
if (dirLocal.lengthSqr() < 0.0001) {
|
||
|
|
dirLocal = v3(0, 1, 0);
|
||
|
|
} else {
|
||
|
|
dirLocal.normalize();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
skill.setMoveDir(dirLocal);
|
||
|
|
|
||
|
|
// 子弹朝向飞行方向(0°=向上)
|
||
|
|
const rawAngle = (Math.atan2(dirLocal.x, dirLocal.y) * 180) / Math.PI;
|
||
|
|
zidan.angle = -rawAngle;
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
gaoyaguoAttack(zidanNum: number, startNode: Node) {
|
||
|
|
//创建子弹打高压锅
|
||
|
|
|
||
|
|
// console.log('gaoyaguoAttack', zidanNum)
|
||
|
|
|
||
|
|
|
||
|
|
let zidanCoreNode = this.ui_center.getChildByName('zidanCore')
|
||
|
|
const door = startNode.getComponent(atgDoor);
|
||
|
|
const sourceDoorNumber = door?.doorNumber ?? zidanNum;
|
||
|
|
// 子弹在出生点做横向“平铺”偏移,避免完全重叠
|
||
|
|
const spacing = 36; // 世界坐标间距(按子弹尺寸可调)
|
||
|
|
const centerIndex = (zidanNum - 1) / 2;
|
||
|
|
const guoWorldPos = this.gaoyaguo_guo.worldPosition.clone();
|
||
|
|
//gg.audio.playEffect({ name: '彩虹飞弹发射', path: 'sound/武器音效/' });
|
||
|
|
for (let i = 0; i < zidanNum; i++) {
|
||
|
|
let zidan = this.zidanCore.getZidan()
|
||
|
|
zidan.scale = v3(1, 1, 1);
|
||
|
|
const startWorld = startNode.worldPosition.clone();
|
||
|
|
startWorld.x += (i - centerIndex) * spacing;
|
||
|
|
zidanCoreNode.addChild(zidan)
|
||
|
|
// 先挂到正确父节点,再设置 worldPosition,避免 reparent 导致世界坐标被改写
|
||
|
|
zidan.worldPosition = startWorld
|
||
|
|
|
||
|
|
const skill = zidan.getComponent(atgSkill);
|
||
|
|
skill.setSkillData(this.zidanCore);
|
||
|
|
// 这批子弹由某个门生成,短时间内忽略与该门的碰撞,防止递归触发
|
||
|
|
skill.setIgnoreDoor(startNode, sourceDoorNumber >= 5 ? 30 : 18);
|
||
|
|
//计算子弹方向:从子弹出生点指向高压锅(每发单独 clone,避免 Vec3.subtract 污染下一发)
|
||
|
|
const dirWorld = guoWorldPos.clone().subtract(startWorld);
|
||
|
|
dirWorld.z = 0;
|
||
|
|
dirWorld.normalize();
|
||
|
|
const dirLocal = this._worldDirToZidanCoreLocalDir(zidanCoreNode, startWorld, dirWorld);
|
||
|
|
skill.setMoveDir(dirLocal);
|
||
|
|
|
||
|
|
// 子弹朝向飞行方向(0°=向上)
|
||
|
|
const rawAngle = (Math.atan2(dirLocal.x, dirLocal.y) * 180) / Math.PI;
|
||
|
|
zidan.angle = -rawAngle;
|
||
|
|
|
||
|
|
zidan.active = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
update(dt: number) {
|
||
|
|
if (this.isGameOver) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if (this.jiedianIndex == 3 || this.jiedianIndex == 4) {
|
||
|
|
this.sendZidanTime += dt
|
||
|
|
if (this.sendZidanTime >= this.sendZidanTimeFix) {
|
||
|
|
this.sendZidanTime = 0
|
||
|
|
//发送子弹
|
||
|
|
this.sendZidan()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!this._sGame) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.gaoyaguoTime += dt
|
||
|
|
if (this.gaoyaguoTime >= this.gaoyaguoMaxTime) {
|
||
|
|
this.gaoyaguoTime = 0;
|
||
|
|
//高压锅向y轴向下移动
|
||
|
|
let moveDiry = -this.gaoyaguospeend
|
||
|
|
let posi = this.gaoyaguoNode.position.clone()
|
||
|
|
posi.y += moveDiry
|
||
|
|
this.gaoyaguoNode.setPosition(posi)
|
||
|
|
if (posi.y <= -500) {
|
||
|
|
//游戏结束失败
|
||
|
|
this.gameoverFail()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
sendZidan() {
|
||
|
|
// 发射子弹:一次尽量 3 颗(不足则 1 颗),并排间隔 18
|
||
|
|
// 索敌范围 1000:优先取 monsterLayer 里 y 最小的组节点,再在该组里找离主角最近的怪物
|
||
|
|
const dbg = this.debugSendZidan;
|
||
|
|
if (dbg) {
|
||
|
|
console.log('[sendZidan] tick', {
|
||
|
|
jiedianIndex: this.jiedianIndex,
|
||
|
|
zhujueTakeZidanNum: this.zhujueTakeZidanNum,
|
||
|
|
monsterGroups: this.monsterLayer?.children?.length ?? -1,
|
||
|
|
zidanCoreChildren: this.ui_center?.getChildByName('zidanCore')?.children?.length ?? -1,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (this.zhujueTakeZidanNum <= 0) {
|
||
|
|
if (dbg) console.log('[sendZidan] 子弹数目不够')
|
||
|
|
//缺弹提示+求助字体
|
||
|
|
this.quedanNode.active = true
|
||
|
|
this.zhuzhutaiNode.active = true
|
||
|
|
//播放动画一次
|
||
|
|
if (this.zhujueSpineAnimation != '待机') {
|
||
|
|
this.zhujueSpineAnimation = '待机'
|
||
|
|
this.zhujueSpine.getComponent(sp.Skeleton).setAnimation(0, this.zhujueSpineAnimation, true);
|
||
|
|
}
|
||
|
|
if (!this.isPlayAnim) {
|
||
|
|
this.isPlayAnim = true
|
||
|
|
tween(this.quedanNode)
|
||
|
|
.to(0.1, { scale: v3(0.1, 0.1, 0.1) })
|
||
|
|
.to(0.5, { scale: v3(0.5, 0.5, 0.5) })
|
||
|
|
.union()
|
||
|
|
.repeatForever()
|
||
|
|
.start()
|
||
|
|
tween(this.zhuzhutaiNode)
|
||
|
|
.to(0.1, { scale: v3(0.1, 0.1, 0.1) })
|
||
|
|
.to(0.5, { scale: v3(0.5, 0.5, 0.5) })
|
||
|
|
.union()
|
||
|
|
.repeatForever()
|
||
|
|
.start()
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!this.monsterLayer || !this.zhujueNode) {
|
||
|
|
if (dbg) console.log('[sendZidan] fallback: monsterLayer/zhujueNode missing -> shoot up');
|
||
|
|
}
|
||
|
|
this.quedanNode.active = false
|
||
|
|
this.zhuzhutaiNode.active = false
|
||
|
|
if (this.zhujueSpineAnimation == '待机') {
|
||
|
|
this.zhujueSpineAnimation = '攻击'
|
||
|
|
this.zhujueSpine.getComponent(sp.Skeleton).setAnimation(0, this.zhujueSpineAnimation, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
const zidanCoreNode = this.ui_center?.getChildByName('zidanCore') ?? null;
|
||
|
|
const parentTrans = zidanCoreNode?.getComponent(UITransform) ?? null;
|
||
|
|
if (!zidanCoreNode || !parentTrans) {
|
||
|
|
if (dbg) console.log('[sendZidan] abort: zidanCoreNode/UITransform missing');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const range = 360;
|
||
|
|
const heroWorld = this.zhujueNode.worldPosition.clone();
|
||
|
|
|
||
|
|
// 1) 找到 monsterLayer 中 y 最小的节点(最靠近主角)
|
||
|
|
let groupNode: Node | null = null;
|
||
|
|
let minY = Number.POSITIVE_INFINITY;
|
||
|
|
for (const n of this.monsterLayer.children) {
|
||
|
|
if (!n?.isValid || n.children.length <= 0) continue;
|
||
|
|
const y = n.worldPosition.y;
|
||
|
|
if (y < minY) {
|
||
|
|
minY = y;
|
||
|
|
groupNode = n;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!groupNode) {
|
||
|
|
if (dbg) console.log('[sendZidan] fallback: no monster group -> shoot up');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2) 在该组内找离主角最近的怪物(优先 child,fallback 用组节点)
|
||
|
|
let targetNode: Node | null = null;
|
||
|
|
let bestD2 = range * range + 1;
|
||
|
|
const candidates = groupNode.children.length > 0 ? groupNode.children : [groupNode];
|
||
|
|
for (const c of candidates) {
|
||
|
|
if (!c?.isValid) continue;
|
||
|
|
if (!c.active) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
const p = c.worldPosition;
|
||
|
|
const dx = p.x - heroWorld.x;
|
||
|
|
const dy = p.y - heroWorld.y;
|
||
|
|
const d2 = dx * dx + dy * dy;
|
||
|
|
if (d2 < bestD2) {
|
||
|
|
bestD2 = d2;
|
||
|
|
targetNode = c;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!targetNode) {
|
||
|
|
if (dbg) console.log('[sendZidan] fallback: no target in group -> shoot up');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 超出索敌范围不发射,也不扣数
|
||
|
|
const hasTargetInRange = !!targetNode && bestD2 <= range * range;
|
||
|
|
if (!hasTargetInRange) {
|
||
|
|
if (dbg) console.log('[sendZidan] fallback: target out of range -> shoot up', { bestD2, range2: range * range });
|
||
|
|
if (this.zhujueSpineAnimation != '待机') {
|
||
|
|
this.zhujueSpineAnimation = '待机'
|
||
|
|
this.zhujueSpine.getComponent(sp.Skeleton).setAnimation(0, this.zhujueSpineAnimation, true);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let fireCount = this.zhujueTakeZidanNum >= 3 ? 3 : 1;
|
||
|
|
//
|
||
|
|
if (this.jiedianIndex == 4) {
|
||
|
|
fireCount = this.zhujueTakeZidanNum >= 6 ? 6 : 1;
|
||
|
|
}
|
||
|
|
const spacing = 36;
|
||
|
|
const centerIndex = (fireCount - 1) / 2;
|
||
|
|
|
||
|
|
const targetWorld = hasTargetInRange ? targetNode!.worldPosition.clone() : heroWorld.clone().add(v3(0, 1000, 0));
|
||
|
|
const heroLocalBase = parentTrans.convertToNodeSpaceAR(heroWorld);
|
||
|
|
const targetLocalFixed = parentTrans.convertToNodeSpaceAR(targetWorld);
|
||
|
|
if (dbg) {
|
||
|
|
console.log('[sendZidan] fire', {
|
||
|
|
fireCount,
|
||
|
|
heroWorld: { x: heroWorld.x, y: heroWorld.y },
|
||
|
|
targetWorld: { x: targetWorld.x, y: targetWorld.y },
|
||
|
|
targetName: hasTargetInRange ? targetNode!.name : 'fallback_up',
|
||
|
|
groupName: groupNode?.name ?? 'none',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
//gg.audio.playEffect({ name: '彩虹飞弹发射', path: 'sound/武器音效/' });
|
||
|
|
for (let i = 0; i < fireCount; i++) {
|
||
|
|
const zidan = this.zidanCore.getZidan();
|
||
|
|
zidanCoreNode.addChild(zidan);
|
||
|
|
|
||
|
|
const skill = zidan.getComponent(atgSkill);
|
||
|
|
skill.setSkillData(this.zidanCore, 1);
|
||
|
|
skill.owner = 'player';
|
||
|
|
|
||
|
|
// 统一用 zidanCore 本地坐标生成与移动(避免 world 坐标在多层级缩放/移动下产生偏差,导致“只在左边发射/屏外”)
|
||
|
|
const startLocal = heroLocalBase.clone();
|
||
|
|
startLocal.x += (i - centerIndex) * spacing;
|
||
|
|
// 主角子弹出生点略微前移,避免“贴脸怪物”瞬间命中回收导致肉眼看不到
|
||
|
|
startLocal.y += 80;
|
||
|
|
zidan.setPosition(startLocal.x, startLocal.y, 0);
|
||
|
|
// 提升到最上层并强制缩放,避免被遮挡/缩放过小
|
||
|
|
zidan.active = true;
|
||
|
|
zidan.setSiblingIndex(zidanCoreNode.children.length - 1);
|
||
|
|
zidan.scale = v3(0.6, 0.6, 0.6);
|
||
|
|
const dirLocal = targetLocalFixed.clone().subtract(startLocal);
|
||
|
|
dirLocal.z = 0;
|
||
|
|
if (dirLocal.lengthSqr() < 0.0001) {
|
||
|
|
skill.setMoveDir(v3(0, 1, 0));
|
||
|
|
zidan.angle = 0;
|
||
|
|
} else {
|
||
|
|
dirLocal.normalize();
|
||
|
|
// 0°=向上;你定义:45=左边,-45=右边
|
||
|
|
const rawAngle = (Math.atan2(dirLocal.x, dirLocal.y) * 180) / Math.PI;
|
||
|
|
// 原始角度可能出现 ±180(目标在身后),这里强制限制在 [-90, 90],避免子弹反向飞
|
||
|
|
const clampedAngle = Math.max(-90, Math.min(90, -rawAngle));
|
||
|
|
const rad = (clampedAngle * Math.PI) / 180;
|
||
|
|
// 约定:0°=向上;+45=左;-45=右(所以 x 要取 -sin)
|
||
|
|
const clampedDirLocal = v3(-Math.sin(rad), Math.cos(rad), 0);
|
||
|
|
// 若 zidanCore(或其父链)做了水平镜像缩放(worldScale.x < 0),本地 X 方向会与世界左右相反,需要修正
|
||
|
|
const wsx = zidanCoreNode.getWorldScale().x;
|
||
|
|
const signX = wsx === 0 ? 1 : wsx > 0 ? 1 : -1;
|
||
|
|
clampedDirLocal.x *= signX;
|
||
|
|
skill.setMoveDir(clampedDirLocal);
|
||
|
|
zidan.angle = clampedAngle * signX;
|
||
|
|
}
|
||
|
|
if (dbg) console.log('[sendZidan] spawnLocal/dirLocal', { i, startLocal: { x: startLocal.x, y: startLocal.y }, dir: { x: skill.moveDir.x, y: skill.moveDir.y } });
|
||
|
|
}
|
||
|
|
|
||
|
|
// 一次性扣除并刷新显示
|
||
|
|
this.zhujueTakeZidanNum -= fireCount;
|
||
|
|
if (this.zhujueTakeZidanNum < 0) this.zhujueTakeZidanNum = 0;
|
||
|
|
if (dbg) console.log('[sendZidan] after', { remaining: this.zhujueTakeZidanNum });
|
||
|
|
const zidanNumNode = this.zhujueNode.getChildByName('zidanNum');
|
||
|
|
if (zidanNumNode) {
|
||
|
|
zidanNumNode.active = true;
|
||
|
|
const lab = zidanNumNode.getComponent(Label);
|
||
|
|
if (lab) lab.string = this.zhujueTakeZidanNum.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
gameoverFail() {
|
||
|
|
let baozha = this.gaoyaguoNode.getChildByName('爆炸4')
|
||
|
|
this.gaoyaguoNode.getChildByName('锅').active = false
|
||
|
|
|
||
|
|
this.gaoyaguoNode.getChildByName('光门1').active = false
|
||
|
|
this.gaoyaguoNode.getChildByName('光门2').active = false
|
||
|
|
gg.audio.playEffect({ name: '汽水炸弹爆炸', path: 'sound/武器音效/' });
|
||
|
|
|
||
|
|
baozha.active = true
|
||
|
|
this.scheduleOnce(() => {
|
||
|
|
baozha.active = false
|
||
|
|
this.gameover()
|
||
|
|
}, 0.5)
|
||
|
|
console.log('游戏结束失败')
|
||
|
|
this._sGame = false
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
gameover() {
|
||
|
|
if (this.isGameOver) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._sGame = false
|
||
|
|
this.isGameOver = true
|
||
|
|
console.log('游戏结束失败')
|
||
|
|
this.node.emit('_onMiniGameEnd', false);
|
||
|
|
}
|
||
|
|
gameoverSuccess() {
|
||
|
|
if (this.isGameOver) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._sGame = false
|
||
|
|
this.isGameOver = true
|
||
|
|
console.log('游戏结束成功')
|
||
|
|
this.node.emit('_onMiniGameEnd', true);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
//发射子弹打高压锅
|
||
|
|
startGame() {
|
||
|
|
this._sGame = true
|
||
|
|
//this.gaoyaguoNode
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
beginMoveScene() {
|
||
|
|
let bg1 = this.ui_center.getChildByName('bg1')
|
||
|
|
let bg2 = this.ui_center.getChildByName('bg2')
|
||
|
|
let zhandou = this.ui_center.getChildByName('zhandou')
|
||
|
|
let menMove = this.ui_center.getChildByName('menMove')
|
||
|
|
|
||
|
|
let scle = 1.1
|
||
|
|
let scle2 = 1.20
|
||
|
|
let posiy = bg1.position.y
|
||
|
|
tween(bg1)
|
||
|
|
.to(1, { y: -809, scale: v3(scle, scle, scle) })
|
||
|
|
.start()
|
||
|
|
|
||
|
|
let posiy2 = bg2.position.y
|
||
|
|
tween(bg2)
|
||
|
|
.to(1, { y: 554, scale: v3(scle2, scle2, scle2) })
|
||
|
|
.start()
|
||
|
|
let posiy3 = zhandou.position.y
|
||
|
|
tween(zhandou)
|
||
|
|
.to(1, { y: 113, scale: v3(scle, scle, scle) })
|
||
|
|
.start()
|
||
|
|
let posiy4 = menMove.position.y
|
||
|
|
tween(menMove)
|
||
|
|
.to(1, { y: -129.5, scale: v3(scle, scle, scle) })
|
||
|
|
.call(() => {
|
||
|
|
this.jiedianIndex = 3
|
||
|
|
this.zhujueTakeZidanNum = 0;
|
||
|
|
const zidanNumNode = this.zhujueNode?.getChildByName('zidanNum');
|
||
|
|
if (zidanNumNode) {
|
||
|
|
zidanNumNode.active = false;
|
||
|
|
const lab = zidanNumNode.getComponent(Label);
|
||
|
|
if (lab) lab.string = '0';
|
||
|
|
}
|
||
|
|
|
||
|
|
GEvent.Ins.emit(GEvent.gaoyaguoBoom, this.jiedianIndex)
|
||
|
|
|
||
|
|
tween(this.moveDoor)
|
||
|
|
.to(3, { x: -270 })
|
||
|
|
.to(3, { x: 0 })
|
||
|
|
.to(3, { x: 270 })
|
||
|
|
.union()
|
||
|
|
.repeatForever()
|
||
|
|
.start()
|
||
|
|
})
|
||
|
|
.start()
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
closeView() {
|
||
|
|
this.node.emit('_onMiniGameClose');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|