feat(ui): 新增段位系统并重构结算弹窗
新增玩家得分段位配置表与相关工具函数,重构战斗结算弹窗: 1. 新增ScoreRankSet.ts实现段位判定、进度计算逻辑 2. 为结算界面添加段位徽章、进度条和晋升动效 3. 实现总分滚动动画与破纪录涨幅显示 4. 调整UI5图集的边框参数适配新UI资源
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3474,10 +3474,10 @@
|
||||
"height": 75,
|
||||
"rawWidth": 75,
|
||||
"rawHeight": 75,
|
||||
"borderTop": 37.5,
|
||||
"borderBottom": 37.5,
|
||||
"borderLeft": 37.5,
|
||||
"borderRight": 37.5,
|
||||
"borderTop": 37,
|
||||
"borderBottom": 37,
|
||||
"borderLeft": 37,
|
||||
"borderRight": 37,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
@@ -3520,10 +3520,10 @@
|
||||
"height": 75,
|
||||
"rawWidth": 75,
|
||||
"rawHeight": 75,
|
||||
"borderTop": 37.5,
|
||||
"borderBottom": 37.5,
|
||||
"borderLeft": 37.5,
|
||||
"borderRight": 37.5,
|
||||
"borderTop": 37,
|
||||
"borderBottom": 37,
|
||||
"borderLeft": 37,
|
||||
"borderRight": 37,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
@@ -3612,10 +3612,10 @@
|
||||
"height": 65,
|
||||
"rawWidth": 65,
|
||||
"rawHeight": 65,
|
||||
"borderTop": 32.5,
|
||||
"borderBottom": 32.5,
|
||||
"borderLeft": 32.5,
|
||||
"borderRight": 32.5,
|
||||
"borderTop": 32,
|
||||
"borderBottom": 32,
|
||||
"borderLeft": 32,
|
||||
"borderRight": 32,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
80
assets/script/game/common/config/ScoreRankSet.ts
Normal file
80
assets/script/game/common/config/ScoreRankSet.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 玩家得分段位配置
|
||||
* 依据玩家历史最高分(smc.data.score)划分段位:黄铜 -> 传说
|
||||
*/
|
||||
|
||||
/** 段位标识 */
|
||||
export enum ScoreTier {
|
||||
BRONZE = "bronze", // 黄铜
|
||||
SILVER = "silver", // 白银
|
||||
GOLD = "gold", // 黄金
|
||||
PLATINUM = "platinum", // 铂金
|
||||
DIAMOND = "diamond", // 钻石
|
||||
MASTER = "master", // 大师
|
||||
LEGEND = "legend", // 传说
|
||||
}
|
||||
|
||||
/** 段位定义(按 minScore 升序排列) */
|
||||
export interface ScoreRankInfo {
|
||||
/** 段位标识 */
|
||||
tier: ScoreTier;
|
||||
/** 段位中文名 */
|
||||
name: string;
|
||||
/** 进入该段位所需的最低分(含) */
|
||||
minScore: number;
|
||||
/** 段位图标帧名(gui/ui6 图集内的 SpriteFrame 名,UI 层经 smc.uiconsAtlas 取帧) */
|
||||
icon: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 段位阈值表:score >= minScore 即达到对应段位
|
||||
* 顺序必须保持 minScore 升序,供二分/逆序查找使用
|
||||
*/
|
||||
export const ScoreRankList: ScoreRankInfo[] = [
|
||||
{ tier: ScoreTier.BRONZE, name: "黄铜", minScore: 0, icon: "League_1_Bronze" },
|
||||
{ tier: ScoreTier.SILVER, name: "白银", minScore: 2000, icon: "League_2_Silver" },
|
||||
{ tier: ScoreTier.GOLD, name: "黄金", minScore: 5000, icon: "League_3_Gold" },
|
||||
{ tier: ScoreTier.PLATINUM, name: "铂金", minScore: 10000, icon: "League_4_Platinum" },
|
||||
{ tier: ScoreTier.DIAMOND, name: "钻石", minScore: 20000, icon: "League_5_Diamond" },
|
||||
{ tier: ScoreTier.MASTER, name: "大师", minScore: 40000, icon: "League_6_Master" },
|
||||
{ tier: ScoreTier.LEGEND, name: "传说", minScore: 80000, icon: "League_7_Legendary" },
|
||||
];
|
||||
|
||||
/**
|
||||
* 根据分数获取当前段位
|
||||
* @param score 玩家分数(通常传历史最高分 smc.data.score)
|
||||
* @returns 当前段位信息;score 为负时保底返回黄铜
|
||||
*/
|
||||
export function getRankByScore(score: number): ScoreRankInfo {
|
||||
// 逆序遍历,命中第一个 minScore <= score 的段位即为当前段位
|
||||
for (let i = ScoreRankList.length - 1; i >= 0; i--) {
|
||||
if (score >= ScoreRankList[i].minScore) {
|
||||
return ScoreRankList[i];
|
||||
}
|
||||
}
|
||||
return ScoreRankList[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分数获取下一段位目标
|
||||
* @param score 玩家分数
|
||||
* @returns 下一段位信息;已是传说(最高段位)时返回 null
|
||||
*/
|
||||
export function getNextRank(score: number): ScoreRankInfo | null {
|
||||
const current = getRankByScore(score);
|
||||
const idx = ScoreRankList.indexOf(current);
|
||||
return idx < ScoreRankList.length - 1 ? ScoreRankList[idx + 1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前分数在当前段位内的进度
|
||||
* @param score 玩家分数
|
||||
* @returns 0~1 的进度值;已是最高段位时恒为 1
|
||||
*/
|
||||
export function getRankProgress(score: number): number {
|
||||
const current = getRankByScore(score);
|
||||
const next = getNextRank(score);
|
||||
if (!next) return 1;
|
||||
const range = next.minScore - current.minScore;
|
||||
return range > 0 ? Math.min((score - current.minScore) / range, 1) : 1;
|
||||
}
|
||||
9
assets/script/game/common/config/ScoreRankSet.ts.meta
Normal file
9
assets/script/game/common/config/ScoreRankSet.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "577b2b8c-c387-4c64-bd45-9ea58980397a",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -2,57 +2,60 @@
|
||||
* @file VictoryComp.ts
|
||||
* @description 战斗结算弹窗组件(UI 视图层)
|
||||
*
|
||||
|
||||
* 通过 oops.gui.open(UIID.Victory, args) 打开。
|
||||
* 展示段位徽章、总分滚动动画、段位进度与亮点成就,并提供重开 / 退出操作。
|
||||
*/
|
||||
import { _decorator, instantiate, Label, Prefab, Node, Sprite, Animation, AnimationClip, resources, UITransform, Widget, ProgressBar, Tween, NodeEventType } from "cc";
|
||||
import { _decorator, instantiate, Label, Prefab, Node, Sprite, ProgressBar, Tween, tween, v3, UIOpacity } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { FacSet } from "../common/config/GameSet";
|
||||
import { HeroInfo } from "../common/config/heroSet";
|
||||
import { CKind, CardType, CardConfig } from "../common/config/CardSet";
|
||||
import { CardComp } from "./CardComp";
|
||||
import { HighlightSet, HighlightType, HighlightLevel } from "../common/config/HighlightSet";
|
||||
import { getRankByScore, getNextRank, getRankProgress, ScoreRankInfo } from "../common/config/ScoreRankSet";
|
||||
import { LangPrefix, lang, langf } from "../common/LangUtil";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 总分滚动动画时长(秒) */
|
||||
const SCORE_TWEEN_DURATION = 1.6;
|
||||
/** 段位晋升动效时长(秒) */
|
||||
const RANK_UP_DURATION = 0.5;
|
||||
|
||||
/**
|
||||
* VictoryComp —— 战斗结算弹窗视图组件
|
||||
*
|
||||
* 通过 oops.gui.open(UIID.Victory, args) 打开。
|
||||
* 展示战斗结果,计算总分,并提供重开 / 退出操作。
|
||||
* 布局(自上而下):
|
||||
* rank_icon 段位徽章
|
||||
* total 总分(score_label 滚动数字)
|
||||
* rank_progress 段位内进度条
|
||||
* next_rank_label 下一段位提示
|
||||
* gain_label +N 涨幅标签(仅破纪录时显示)
|
||||
* highlights 亮点成就标签容器
|
||||
* btns 操作按钮
|
||||
*/
|
||||
@ccclass('VictoryComp')
|
||||
@ecs.register('Victory', false)
|
||||
export class VictoryComp extends CCComp {
|
||||
|
||||
@property(Node)
|
||||
mvp_node = null!
|
||||
|
||||
// ======================== 结算 UI 绑定 ========================
|
||||
@property({ type: Label, tooltip: "总分文本" })
|
||||
|
||||
@property({ type: Sprite, tooltip: "段位徽章图标(rank_icon 节点上的 Sprite)" })
|
||||
rank_icon: Sprite = null!;
|
||||
|
||||
@property({ type: Label, tooltip: "总分文本(total/score_label)" })
|
||||
total_score_label: Label = null!;
|
||||
|
||||
@property({ type: Node, tooltip: "战绩分节点 (需包含 score_label 和 progress_bar 子节点)" })
|
||||
combat_node: Node = null!;
|
||||
@property({ type: ProgressBar, tooltip: "段位进度条(rank_progress)" })
|
||||
rank_progress: ProgressBar = null!;
|
||||
|
||||
@property({ type: Node, tooltip: "输出分节点 (需包含 score_label 和 progress_bar 子节点)" })
|
||||
output_node: Node = null!;
|
||||
@property({ type: Label, tooltip: "下一段位提示文本(next_rank_label)" })
|
||||
next_rank_label: Label = null!;
|
||||
|
||||
@property({ type: Node, tooltip: "防御分节点 (需包含 score_label 和 progress_bar 子节点)" })
|
||||
defense_node: Node = null!;
|
||||
|
||||
@property({ type: Node, tooltip: "构建分节点 (需包含 score_label 和 progress_bar 子节点)" })
|
||||
build_node: Node = null!;
|
||||
|
||||
@property({ type: Node, tooltip: "效率分节点 (需包含 score_label 和 progress_bar 子节点)" })
|
||||
efficiency_node: Node = null!;
|
||||
@property({ type: Label, tooltip: "涨幅标签(gain_label,仅破纪录时显示)" })
|
||||
gain_label: Label = null!;
|
||||
|
||||
@property({ type: Node, tooltip: "亮点成就标签的容器" })
|
||||
highlights_container: Node = null!;
|
||||
@@ -75,6 +78,19 @@ export class VictoryComp extends CCComp {
|
||||
diamond: 0
|
||||
}
|
||||
|
||||
// ======================== 动画状态 ========================
|
||||
|
||||
/** 动画期间实时显示的分数(tween 驱动) */
|
||||
private _displayScore: number = 0;
|
||||
/** 滚动前的历史最高分(动画起始值) */
|
||||
private _prevBestScore: number = 0;
|
||||
/** 本局总分(动画结束值) */
|
||||
private _finalScore: number = 0;
|
||||
/** 滚动起始段位(用于检测跨段) */
|
||||
private _startRank: ScoreRankInfo = null!;
|
||||
/** 动画期间是否已触发过段位晋升动效 */
|
||||
private _rankUpPlayed: boolean = false;
|
||||
|
||||
// ======================== 复活相关 ========================
|
||||
|
||||
/** 是否可以复活(由 MissionComp 传入,取决于剩余复活次数) */
|
||||
@@ -103,105 +119,17 @@ export class VictoryComp extends CCComp {
|
||||
// 根据是否可复活决定按钮显示
|
||||
this.node.getChildByName("btns").getChildByName("next").active = !args.can_revive
|
||||
|
||||
// 计算总分
|
||||
// 计算总分并写入历史纪录
|
||||
this.calculateTotalScore();
|
||||
|
||||
// 渲染分数UI和亮点标签
|
||||
this.renderScores();
|
||||
// 播放结算动画
|
||||
this.playSettleAnimation();
|
||||
|
||||
// 显示MVP英雄
|
||||
const mvp = this.getMVPHero();
|
||||
this.renderMVPHero(mvp);
|
||||
// 渲染亮点标签
|
||||
this.renderHighlights();
|
||||
}
|
||||
|
||||
// ======================== MVP 英雄 ========================
|
||||
|
||||
/**
|
||||
* 获取战斗中最厉害的英雄(根据等级和攻击力)
|
||||
*/
|
||||
private getMVPHero(): HeroAttrsComp | null {
|
||||
let mvp: HeroAttrsComp | null = null;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
|
||||
const model = entity.get(HeroAttrsComp);
|
||||
if (!model || model.fac !== FacSet.HERO) return;
|
||||
if (!mvp) {
|
||||
mvp = model;
|
||||
} else {
|
||||
if (model.lv > mvp.lv) {
|
||||
mvp = model;
|
||||
} else if (model.lv === mvp.lv && model.ap > mvp.ap) {
|
||||
mvp = model;
|
||||
}
|
||||
}
|
||||
});
|
||||
return mvp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染 MVP 英雄,使用绑定的 CardComp
|
||||
*/
|
||||
private renderMVPHero(mvp: HeroAttrsComp | null) {
|
||||
if (!this.mvp_node) return;
|
||||
|
||||
if (!mvp) {
|
||||
this.mvp_node.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.mvp_node.active = true;
|
||||
|
||||
const uuid = mvp.hero_uuid;
|
||||
const hero = HeroInfo[uuid];
|
||||
if (!hero) return;
|
||||
|
||||
// 延迟到下一帧执行,因为如果 mvp_node 刚被激活,Cocos 会在当前帧稍后/下一帧触发 CardComp 的 onLoad
|
||||
// 而 CardComp 的 onLoad 中调用了 applyEmptyUI() 会把卡面清空,导致我们的设置被覆盖。
|
||||
this.scheduleOnce(() => {
|
||||
if (!this.isValid || !this.mvp_node) return;
|
||||
|
||||
// 获取 CardComp 组件
|
||||
let cardComp = this.mvp_node.getComponent(CardComp);
|
||||
if (!cardComp) {
|
||||
cardComp = this.mvp_node.addComponent(CardComp);
|
||||
}
|
||||
|
||||
// 构造虚拟的 CardConfig 数据供渲染
|
||||
const cardConfig: CardConfig = {
|
||||
uuid: uuid,
|
||||
type: CardType.Hero,
|
||||
cost: 0,
|
||||
weight: 0,
|
||||
kind: CKind.Hero,
|
||||
card_lv: mvp.card_lv || 1,
|
||||
hero_lv: mvp.lv || 1,
|
||||
base_card_lv: mvp.base_card_lv || mvp.card_lv || 1,
|
||||
};
|
||||
|
||||
const originalPos = this.mvp_node.position.clone();
|
||||
|
||||
// 禁用交互事件,防止结算界面的卡牌被拖拽或点击
|
||||
this.mvp_node.off(NodeEventType.TOUCH_START);
|
||||
this.mvp_node.off(NodeEventType.TOUCH_MOVE);
|
||||
this.mvp_node.off(NodeEventType.TOUCH_END);
|
||||
this.mvp_node.off(NodeEventType.TOUCH_CANCEL);
|
||||
if (cardComp.Lock) cardComp.Lock.off(NodeEventType.TOUCH_END);
|
||||
if (cardComp.unLock) cardComp.unLock.off(NodeEventType.TOUCH_END);
|
||||
|
||||
// 应用数据并刷新UI
|
||||
cardComp.applyDrawCard(cardConfig);
|
||||
|
||||
// 隐藏不必要的信息(比如费用)
|
||||
if (cardComp.cost_node) {
|
||||
cardComp.cost_node.active = false;
|
||||
}
|
||||
|
||||
// 覆盖 CardComp 内部动画的 scale,停止其上的 tween,并直接放大
|
||||
// 结算界面的卡牌需要放大显示,170 * 1.35 ≈ 230
|
||||
Tween.stopAllByTarget(this.mvp_node);
|
||||
this.mvp_node.setPosition(originalPos);
|
||||
this.mvp_node.setScale(1.35, 1.35, 1);
|
||||
}, 0);
|
||||
}
|
||||
// ======================== 分数计算(逻辑层,保持不变) ========================
|
||||
|
||||
/**
|
||||
* 获取满足条件的最高等级的亮点成就
|
||||
@@ -257,6 +185,7 @@ export class VictoryComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 计算单局总分并更新到 smc.vmdata.scores.score。
|
||||
* 同时刷新 smc.data.score 历史最高分。
|
||||
*/
|
||||
private calculateTotalScore() {
|
||||
const s = smc.vmdata.scores;
|
||||
@@ -298,6 +227,10 @@ export class VictoryComp extends CCComp {
|
||||
// 取整并存储当前局分数
|
||||
s.score = Math.floor(s.score_combat + s.score_output + s.score_defense + s.score_build + s.score_efficiency + highlightBonus);
|
||||
|
||||
// 记录滚动前的历史最高分(动画起始值)
|
||||
this._prevBestScore = smc.data.score;
|
||||
this._finalScore = s.score;
|
||||
|
||||
// 判定是否打破历史最高分记录
|
||||
let isNewRecord = false;
|
||||
if (s.score > smc.data.score) {
|
||||
@@ -320,53 +253,184 @@ export class VictoryComp extends CCComp {
|
||||
efficiency: s.score_efficiency,
|
||||
highlightBonus: highlightBonus,
|
||||
achievedHighlights: achieved,
|
||||
isNewRecord: isNewRecord
|
||||
isNewRecord: isNewRecord,
|
||||
prevBest: this._prevBestScore
|
||||
});
|
||||
}
|
||||
|
||||
// ======================== 结算动画 ========================
|
||||
|
||||
/**
|
||||
* 渲染得分条与亮点标签
|
||||
* 依赖各维度对应的UI节点(需要在Cocos Creator中拖入绑定)
|
||||
* 播放结算动画:入场 → 总分滚动 → 定格涨幅
|
||||
*/
|
||||
private renderScores() {
|
||||
const s = smc.vmdata.scores;
|
||||
private playSettleAnimation() {
|
||||
// 初始化动画状态
|
||||
this._displayScore = this._prevBestScore;
|
||||
this._startRank = getRankByScore(this._prevBestScore);
|
||||
this._rankUpPlayed = false;
|
||||
|
||||
// 渲染总分
|
||||
// 初始渲染:旧纪录分数与段位
|
||||
this.renderScore(this._prevBestScore);
|
||||
this.renderRank(this._prevBestScore);
|
||||
|
||||
// 隐藏涨幅标签
|
||||
if (this.gain_label) {
|
||||
this.gain_label.node.active = false;
|
||||
}
|
||||
|
||||
// 若本局分数未超过旧纪录,直接定格,无需滚动
|
||||
if (this._finalScore <= this._prevBestScore) {
|
||||
this.showGainLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
// 滚动动画:从旧纪录涨到本局分数
|
||||
const self = this;
|
||||
const proxy = { value: this._prevBestScore };
|
||||
tween(proxy)
|
||||
.to(SCORE_TWEEN_DURATION, { value: this._finalScore }, {
|
||||
easing: 'quadOut',
|
||||
onUpdate(target) {
|
||||
self.onScoreTweenUpdate(Math.floor(target!.value));
|
||||
}
|
||||
})
|
||||
.call(() => {
|
||||
self.onScoreTweenEnd();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 总分滚动过程中的每一帧更新
|
||||
* @param score 当前插值分数
|
||||
*/
|
||||
private onScoreTweenUpdate(score: number) {
|
||||
this._displayScore = score;
|
||||
this.renderScore(score);
|
||||
this.renderRank(score);
|
||||
|
||||
// 跨段检测:当前分数已超过起始段位的下一段门槛,触发晋升动效
|
||||
const currentRank = getRankByScore(score);
|
||||
if (!this._rankUpPlayed && currentRank.tier !== this._startRank.tier) {
|
||||
this._rankUpPlayed = true;
|
||||
this.playRankUpEffect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 总分滚动结束
|
||||
*/
|
||||
private onScoreTweenEnd() {
|
||||
this._displayScore = this._finalScore;
|
||||
this.renderScore(this._finalScore);
|
||||
this.renderRank(this._finalScore);
|
||||
this.showGainLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染总分数字
|
||||
*/
|
||||
private renderScore(score: number) {
|
||||
if (this.total_score_label) {
|
||||
this.total_score_label.string = `${s.score}`;
|
||||
this.total_score_label.string = `${score}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 判定是否是新记录,如果是则激活 new 节点
|
||||
const isNewRecord = (s as any).isNewRecord === true;
|
||||
const newNode = this.total_score_label.node.getChildByName("new");
|
||||
if (newNode) {
|
||||
newNode.active = isNewRecord;
|
||||
/**
|
||||
* 渲染段位徽章、进度条与下一段位提示
|
||||
* @param score 当前用于判定段位的分数
|
||||
*/
|
||||
private renderRank(score: number) {
|
||||
const rank = getRankByScore(score);
|
||||
const next = getNextRank(score);
|
||||
|
||||
// 段位徽章:从全局图集取帧
|
||||
if (this.rank_icon && smc.uiconsAtlas) {
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(rank.icon);
|
||||
if (frame) {
|
||||
this.rank_icon.spriteFrame = frame;
|
||||
}
|
||||
}
|
||||
|
||||
// 通用渲染单个维度的函数
|
||||
const renderDim = (node: Node, score: number, maxScore: number) => {
|
||||
if (!node) return;
|
||||
const lab = node.getChildByName("score_label")?.getComponent(Label);
|
||||
if (lab) lab.string = `${score}`;
|
||||
// 段位进度条
|
||||
if (this.rank_progress) {
|
||||
this.rank_progress.progress = getRankProgress(score);
|
||||
}
|
||||
|
||||
const bar = node.getChildByName("progress_bar")?.getComponent(ProgressBar);
|
||||
if (bar) {
|
||||
// 根据该维度得分占“预期满分”的比例设置进度条(fillRange)
|
||||
bar.progress = Math.min(1, Math.max(0, score / maxScore));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: 进度条的最大值可按设计期望自行调整,目前为占位预估值
|
||||
// renderDim(this.combat_node, s.score_combat, 3000);
|
||||
// renderDim(this.output_node, s.score_output, 3000);
|
||||
// renderDim(this.defense_node, s.score_defense, 1500);
|
||||
// renderDim(this.build_node, s.score_build, 1000);
|
||||
// renderDim(this.efficiency_node, s.score_efficiency, 200);
|
||||
|
||||
// 渲染成就亮点标签
|
||||
this.renderHighlights();
|
||||
// 下一段位提示
|
||||
if (this.next_rank_label) {
|
||||
this.next_rank_label.string = next
|
||||
? `下一段位:${next.name}(${next.minScore} 分)`
|
||||
: `已达最高段位`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 段位晋升动效:徽章放大回弹 + 闪白
|
||||
*/
|
||||
private playRankUpEffect() {
|
||||
if (!this.rank_icon) return;
|
||||
const node = this.rank_icon.node;
|
||||
|
||||
// 停止徽章上残留的 tween,避免叠加
|
||||
Tween.stopAllByTarget(node);
|
||||
node.setScale(1, 1, 1);
|
||||
|
||||
tween(node)
|
||||
.to(RANK_UP_DURATION * 0.4, { scale: v3(1.4, 1.4, 1) }, { easing: 'quadOut' })
|
||||
.to(RANK_UP_DURATION * 0.6, { scale: v3(1, 1, 1) }, { easing: 'backOut' })
|
||||
.start();
|
||||
|
||||
// 若有 UIOpacity 组件则做一次闪白
|
||||
const op = node.getComponent(UIOpacity);
|
||||
if (op) {
|
||||
Tween.stopAllByTarget(op);
|
||||
tween(op)
|
||||
.to(RANK_UP_DURATION * 0.3, { opacity: 120 })
|
||||
.to(RANK_UP_DURATION * 0.7, { opacity: 255 })
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动定格后显示 +N 涨幅标签(仅破纪录时)
|
||||
*/
|
||||
private showGainLabel() {
|
||||
if (!this.gain_label) return;
|
||||
|
||||
const s = smc.vmdata.scores;
|
||||
const isNewRecord = (s as any).isNewRecord === true;
|
||||
const gain = this._finalScore - this._prevBestScore;
|
||||
|
||||
if (!isNewRecord || gain <= 0) {
|
||||
this.gain_label.node.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.gain_label.string = `+${gain} 新纪录!`;
|
||||
this.gain_label.node.active = true;
|
||||
|
||||
// 浮现动效:从 0 缩放淡入
|
||||
const node = this.gain_label.node;
|
||||
Tween.stopAllByTarget(node);
|
||||
node.setScale(0, 0, 1);
|
||||
|
||||
let op = node.getComponent(UIOpacity);
|
||||
if (!op) {
|
||||
op = node.addComponent(UIOpacity);
|
||||
}
|
||||
op.opacity = 0;
|
||||
|
||||
tween(node)
|
||||
.to(0.35, { scale: v3(1, 1, 1) }, { easing: 'backOut' })
|
||||
.start();
|
||||
tween(op)
|
||||
.to(0.35, { opacity: 255 })
|
||||
.start();
|
||||
}
|
||||
|
||||
// ======================== 亮点成就 ========================
|
||||
|
||||
/**
|
||||
* 根据当前局数据匹配并生成对应的亮点标签(成就)
|
||||
*/
|
||||
@@ -449,6 +513,9 @@ export class VictoryComp extends CCComp {
|
||||
}
|
||||
|
||||
protected onDestroy(): void {
|
||||
// 停止所有动画,防止节点销毁后 tween 回调访问无效对象
|
||||
if (this.rank_icon) Tween.stopAllByTarget(this.rank_icon.node);
|
||||
if (this.gain_label) Tween.stopAllByTarget(this.gain_label.node);
|
||||
super.onDestroy();
|
||||
mLogger.log(this.debugMode, 'VictoryComp', "释放胜利界面");
|
||||
}
|
||||
@@ -457,4 +524,4 @@ export class VictoryComp extends CCComp {
|
||||
reset() {
|
||||
this.node.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user