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.
 
 
 
 
AdLike2048/assets/script/battle/view/DamageTextSpawner.ts

214 lines
7.8 KiB

import { Node, Prefab, Tween, Vec3, instantiate, tween } from 'cc';
import { GBundle } from '../../game/ConfigRes';
import { HurtScore } from '../../ui/BattleGame/HurtScore';
import { DeviceProfile } from './DeviceProfile';
/**
* 伤害飘字:默认 atkMonsterScore;暴击 critHurtScore(数值)+ critHurtScoreZi(字);
* base_attr=6(额外伤害倍数)用 rateDamageScore。
* 唐僧城墙扣血可指定 addHPScore。
* 击退等状态字走 MonsterStatusText(battleText),不在此处。
*/
export class DamageTextSpawner {
/** 怪物伤害飘字相对预制体默认尺寸的显示缩放 */
private static readonly DISPLAY_SCALE = 0.72;
/** 暴击字出生点相对怪物水平偏移(随机左/右) */
private static readonly CRIT_ZI_SIDE_DRIFT = 30;
private static _activeBurst = 0;
/** 离场时自增,作废未完成的爆发回扣 */
private static _burstGen = 0;
/** 对局结束:清爆发计数,忽略未到期的回扣 */
static resetBurst(): void {
DamageTextSpawner._burstGen++;
DamageTextSpawner._activeBurst = 0;
}
spawn(
amount: number,
worldPos: Vec3,
parent: Node,
isCrit: boolean,
extraMul: boolean,
prefabOverride?: string,
): void {
if (!DeviceProfile.enableDamageText) return;
if (!parent?.isValid || amount <= 0) return;
if (DamageTextSpawner._activeBurst >= DeviceProfile.damageTextBurstMax) return;
const useCritPair = !prefabOverride && !extraMul && isCrit;
const prefabName = prefabOverride
|| (extraMul ? 'rateDamageScore' : isCrit ? 'critHurtScore' : 'atkMonsterScore');
this._spawnNumber(amount, worldPos, parent, isCrit, extraMul, prefabName, !!prefabOverride);
if (useCritPair) {
// this._spawnCritZi(worldPos, parent);
}
}
/** 数值飘字:critHurtScore / atkMonsterScore 等,贝塞尔下落 */
private _spawnNumber(
amount: number,
worldPos: Vec3,
parent: Node,
isCrit: boolean,
extraMul: boolean,
prefabName: string,
useFullScale: boolean,
): void {
let node: Node | null = null;
try {
node = gg.res.getNode('prefab/数字/', prefabName, GBundle.BattleGame);
} catch {
return;
}
if (!node?.isValid) return;
DamageTextSpawner._activeBurst++;
const burstGen = DamageTextSpawner._burstGen;
node.setParent(parent);
node.active = true;
node.opacity = 255;
const s = useFullScale ? 1 : DamageTextSpawner.DISPLAY_SCALE;
node.setScale(s, s, 1);
Tween.stopAllByTarget(node);
node.worldPosition = worldPos;
const hurt = node.getComponent(HurtScore);
if (hurt) {
// 暴击字由 critHurtScoreZi 单独飘,数值预制体不再开「暴」子节点
hurt.showHurtNum(amount, isCrit, extraMul, { hideBaoBadge: isCrit && !extraMul });
}
node.setSiblingIndex(parent.children.length - 1);
setTimeout(() => {
if (burstGen !== DamageTextSpawner._burstGen) return;
DamageTextSpawner._activeBurst = Math.max(0, DamageTextSpawner._activeBurst - 1);
}, 900);
}
/**
* 暴击字:头顶上方;动效与原先相同(竖直上飘),仅出生点随机左右偏 ±100。
*/
private _spawnCritZi(worldPos: Vec3, parent: Node): void {
let node: Node | null = null;
try {
node = gg.res.getNode('prefab/数字/', 'critHurtScoreZi', GBundle.BattleGame);
} catch {
return;
}
if (!node?.isValid) return;
node.setParent(parent);
node.active = true;
node.opacity = 255;
const s = DamageTextSpawner.DISPLAY_SCALE;
node.setScale(s, s, 1);
Tween.stopAllByTarget(node);
const side = Math.random() < 0.5 ? -1 : 1;
const ziPos = worldPos.clone();
ziPos.x += side * DamageTextSpawner.CRIT_ZI_SIDE_DRIFT;
ziPos.y += 50;
node.setWorldPosition(ziPos);
this._playGitNormalFloat(node, () => {
if (node?.isValid) gg.res.putNode(node);
});
node.setSiblingIndex(parent.children.length - 1);
}
/**
* git HurtScore 普通伤害飘字:爆发弹出(backOut)→ 回弹 → 短暂停顿 → 上飘淡出。
*/
private _playGitNormalFloat(node: Node, onComplete: () => void): void {
const startPos = node.position.clone();
const baseScale = node.scale.clone();
node.setPosition(startPos);
tween(node)
.set({ opacity: 255, scale: new Vec3(baseScale.x * 0.5, baseScale.y * 0.5, 1) })
.to(0.08, { scale: new Vec3(baseScale.x * 1.35, baseScale.y * 1.35, 1) }, { easing: 'backOut' })
.to(0.1, { scale: new Vec3(baseScale.x, baseScale.y, 1) }, { easing: 'quadOut' })
.delay(0.5)
.to(0.35, {
opacity: 0,
position: new Vec3(startPos.x, startPos.y + 50, 0),
}, { easing: 'sineOut' })
.call(onComplete)
.start();
}
/** 秒杀 / 斩杀飘字(killEff 预制体:子节点「秒杀」「斩杀」) */
spawnKillEffect(
worldPos: Vec3,
parent: Node,
kind: 'instant_kill' | 'execute',
): void {
if (!DeviceProfile.enableDamageText) return;
if (!parent?.isValid) return;
const node = this._borrowKillEffNode();
if (!node?.isValid) return;
const badgeName = kind === 'instant_kill' ? '秒杀' : '斩杀';
const miaoSha = node.getChildByName('秒杀');
const zhanSha = node.getChildByName('斩杀');
if (miaoSha) miaoSha.active = badgeName === '秒杀';
if (zhanSha) zhanSha.active = badgeName === '斩杀';
node.setParent(parent);
node.active = true;
node.opacity = 255;
const s = DamageTextSpawner.DISPLAY_SCALE;
node.setScale(s, s, 1);
Tween.stopAllByTarget(node);
node.setWorldPosition(worldPos);
const startPos = node.position.clone();
const baseScale = node.scale.clone();
tween(node)
.set({ opacity: 255, scale: new Vec3(baseScale.x * 0.5, baseScale.y * 0.5, 1) })
.to(0.08, { scale: new Vec3(baseScale.x * 1.35, baseScale.y * 1.35, 1) }, { easing: 'backOut' })
.to(0.1, { scale: new Vec3(baseScale.x, baseScale.y, 1) }, { easing: 'quadOut' })
.delay(0.12)
.to(0.35, {
opacity: 0,
position: new Vec3(startPos.x, startPos.y + 50, 0),
}, { easing: 'sineOut' })
.call(() => {
gg.res.putNode(node);
})
.start();
node.setSiblingIndex(parent.children.length - 1);
}
private _borrowKillEffNode(): Node | null {
const path = 'prefab/';
const name = 'killEff';
const bundle = GBundle.BattleGame;
let node: Node | null = null;
try {
node = gg.res.getNode(path, name, bundle);
} catch {
node = null;
}
if (node?.isValid) return node;
const prefab = gg.res.getRes(name, path, bundle, Prefab) as Prefab | null;
if (!prefab?.isValid) return null;
node = instantiate(prefab);
node['resKey'] = bundle + path + name;
return node;
}
/** 由 damageType 识别秒杀 / 斩杀飘字种类 */
static resolveKillFloaterKind(damageType?: string): 'instant_kill' | 'execute' | null {
if (!damageType) return null;
if (damageType.includes('instant_kill')) return 'instant_kill';
if (damageType === 'execute' || damageType.endsWith('_execute')) return 'execute';
return null;
}
}