feat(关卡): 添加阶段切换提示动画

新增 tip.prefab 资源作为阶段提示栏,在 MissionComp 中实现 playTooltipAnim 方法。
当关卡阶段切换至特定状态(如胜利、失败等)时,播放从右侧飞入、中央停留、左侧飞出的动感动画,提升阶段切换的视觉反馈和游戏体验。
This commit is contained in:
walkpan
2026-04-23 20:47:04 +08:00
parent cdb29385ce
commit 60352af998
4 changed files with 1100 additions and 926 deletions

View File

@@ -0,0 +1,505 @@
[
{
"__type__": "cc.Prefab",
"_name": "tip",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "tip",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
},
{
"__id__": 10
}
],
"_active": true,
"_components": [
{
"__id__": 20
}
],
"_prefab": {
"__id__": 22
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 640,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "bg",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
},
{
"__id__": 7
}
],
"_prefab": {
"__id__": 9
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -4.773,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 780,
"height": 130
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "1003GdjfpKXJE/TG1yWDuH"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@eb418",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "60UFmTMHJPWp2w+0Gho1h8"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 8
},
"_alignFlags": 40,
"_target": null,
"_left": -30,
"_right": -30,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 428,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63ZPc/lvZEMp22HeC+sVFO"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "4ebCuCwKhIYKWQP8OrCFBS",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "lab",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 11
},
{
"__id__": 13
},
{
"__id__": 15
},
{
"__id__": 17
}
],
"_prefab": {
"__id__": 19
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_contentSize": {
"__type__": "cc.Size",
"width": 670,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "14Q/gAI7JJuIWi5WFPFHjn"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 14
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_string": "这里是信息",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 41,
"_fontSize": 40,
"_fontFamily": "Arial",
"_lineHeight": 40,
"_overflow": 2,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": true,
"_isUnderline": false,
"_underlineHeight": 2,
"_cacheMode": 1,
"_enableOutline": true,
"_outlineColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_outlineWidth": 4,
"_enableShadow": false,
"_shadowColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_shadowOffset": {
"__type__": "cc.Vec2",
"x": 2,
"y": 2
},
"_shadowBlur": 2,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "f4PtuT0TNHiaf1kHgltRjN"
},
{
"__type__": "110c8vEd5NEPL/N9meGQnaX",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 16
},
"_params": [],
"_dataID": "",
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "0bsDdkC31Dx6EJbtMWR83S"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 18
},
"_alignFlags": 45,
"_target": null,
"_left": 25,
"_right": 25,
"_top": 10,
"_bottom": 10,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 600,
"_originalHeight": 54.4,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57At9q+gBFlZN9Nieh/UVp"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "a1CYFX8AxIUpCik88M7h8G",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 21
},
"_contentSize": {
"__type__": "cc.Size",
"width": 720,
"height": 120
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ef3+UDPdVJGoz5oexdMgv2"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "18X8QQ53xKDbV/EH/Cm3i4",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "c09817a2-70d1-4e67-8f4c-60d9ff67bd1f",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "tip"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@
* - CardInitCoins —— 初始金币数
* - UIID.Victory —— 结算弹窗
*/
import { _decorator, Vec3,Animation, instantiate, Prefab, Node, NodeEventType, ProgressBar, Label, CCInteger, tween, v3, Tween } from "cc";
import { _decorator, Vec3,Animation, instantiate, Prefab, Node, NodeEventType, ProgressBar, Label, CCInteger, tween, v3, Tween, Widget, 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 { smc } from "../common/SingletonModuleComp";
@@ -76,6 +76,7 @@ export class MissionComp extends CCComp {
@property({ tooltip: "是否显示战斗内存观测面板" })
private showMemoryPanel: boolean = false;
// ======================== 配置参数 ========================
/** 怪物数量上限(超过后暂停刷怪) */
@@ -100,6 +101,8 @@ export class MissionComp extends CCComp {
/** 时间/波数显示节点 */
@property(Node)
time_node:Node = null!
@property(Node)
tooltip:Node = null!
/** 阶段名称映射表(用于 UI 显示) */
private static readonly PhaseNameMap: Record<MissionPhase, string> = {
@@ -300,6 +303,52 @@ export class MissionComp extends CCComp {
}
}
/** 播放阶段提示栏Tooltip动感切换动画 */
private playTooltipAnim(phaseName: string) {
if (!this.tooltip || !this.tooltip.isValid) {
console.warn("MissionComp: tooltip 节点未绑定或已失效,无法播放阶段提示动画!请在编辑器中将对应节点拖入 tooltip 属性中。");
return;
}
// 禁用 Widget 组件,防止其在 LateUpdate 中覆盖 tween 的位置修改
const widget = this.tooltip.getComponent(Widget);
if (widget) {
widget.enabled = false;
}
const labNode = this.tooltip.getChildByName("lab");
if (labNode) {
const label = labNode.getComponent(Label);
if (label) {
label.string = phaseName;
}
}
this.tooltip.active = true;
Tween.stopAllByTarget(this.tooltip);
// 动感动画设计:右侧进入 -> 屏幕中央(带有轻微的弹跳和滑动) -> 左侧飞出
// 假设屏幕宽度适配下1200是一个足够的屏幕外距离适配横竖屏
const startPos = v3(1200, this.tooltip.position.y, this.tooltip.position.z);
const centerPos = v3(0, this.tooltip.position.y, this.tooltip.position.z);
const driftPos = v3(-50, this.tooltip.position.y, this.tooltip.position.z); // 在中央时的缓慢漂移
const endPos = v3(-1200, this.tooltip.position.y, this.tooltip.position.z);
this.tooltip.setPosition(startPos);
tween(this.tooltip)
// 1. 从右侧快速飞入并带回弹效果 (0.5秒)
.to(0.5, { position: centerPos }, { easing: "backOut" })
// 2. 在屏幕中央缓慢向左漂移,增强动感停留 (1.0秒)
.to(1.0, { position: driftPos }, { easing: "sineInOut" })
// 3. 快速向左飞出并消失 (0.4秒)
.to(0.4, { position: endPos }, { easing: "backIn" })
.call(() => {
this.tooltip.active = false;
})
.start();
}
/**
* 阶段切换核心方法(状态机)
* 处理状态流转时所需的事件触发和全局标志位修改。
@@ -310,6 +359,15 @@ export class MissionComp extends CCComp {
const oldPhase = this.currentPhase;
this.currentPhase = targetPhase;
const phaseName = MissionComp.PhaseNameMap[targetPhase] || "未知";
// 播放状态切换提示栏动效(过滤掉 None、Prepare 准备阶段、Battle 战斗中阶段)
if (targetPhase !== MissionPhase.None &&
targetPhase !== MissionPhase.Prepare &&
targetPhase !== MissionPhase.Battle) {
this.playTooltipAnim(phaseName);
}
// 更新阶段显示 UI
if (this.time_node && this.time_node.isValid) {
const phaseNode = this.time_node.getChildByPath("Phase/Label");