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.
1587 lines
62 KiB
1587 lines
62 KiB
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<number, number>();
|
|
|
|
/**当前怪物身上的所有buff */
|
|
private _allBuff: SpecialSkillAddBuffType[] = [];
|
|
|
|
/**被同种武器攻击次数 */
|
|
private _sameWeaponAtkCount: Map<number, number> = new Map();
|
|
/**被同种武器攻击的累积倍数 */
|
|
private _sameWeaponAtkMultiple: number = 1;
|
|
/**特殊伤害倍数(老婆飞饼每0.5s伤害增幅) */
|
|
private _damageSpecialMultiple: number = 1;
|
|
|
|
private _canHideGuideEff: boolean = false;
|
|
|
|
/**怪物标签状态 */
|
|
private _tagStatus: Map<string, boolean> = 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<Node | null> {
|
|
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
|
|
}
|
|
}
|
|
|
|
|
|
|