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/ui/PanelHome/PanelHomeSceneSwitchPresent...

211 lines
8.0 KiB

7 days ago
import { Component, Node, sp } from 'cc';
import { ensureSpineSkeletonDataRuntime } from 'db://assets/mx/module/lang/spineRuntimePatch';
/** 首页场景节点名 */
const NODE_SCENE = '首页场景';
/** 首页数字节点名 */
const NODE_NUM = '首页数字';
/** 数字待机动画 */
const ANIM_IDLE = '待机';
/** 数字场景变换动画 */
const ANIM_SWITCH = '场景变换';
/** 变换动画内切场景的事件名 */
const EVENT_SWITCH = '开始渐隐变换场景';
/** 场景皮肤列表 */
const SCENE_SKINS = ['初始', '熔浆', '覆水', '雪地'] as const;
/** 切换间隔下限(秒) */
const INTERVAL_MIN = 3;
/** 切换间隔上限(秒) */
const INTERVAL_MAX = 5;
/**
* 首页场景皮肤随机切换:间隔 3~5 秒,播「场景变换」,在事件帧切皮肤,播完回待机。
*/
export class PanelHomeSceneSwitchPresenter {
private _host: Component | null = null;
private _levelSelect: Node | null = null;
private _sceneSkel: sp.Skeleton | null = null;
private _numSkel: sp.Skeleton | null = null;
private _skinIndex = 0;
private _switching = false;
/**
* 绑定宿主与 ui_levelSelect,解析 Spine 并开局待机。
* @param host 用于 schedule 的组件(PanelHome)
* @param levelSelect ui_levelSelect
*/
bind(host: Component | null | undefined, levelSelect: Node | null | undefined): void {
this.unbind();
if (!host?.isValid || !levelSelect?.isValid) {
console.log('[PanelHomeSceneSwitch] 绑定参数无效');
return;
}
this._host = host;
this._levelSelect = levelSelect;
this._sceneSkel = this._resolveSkel(NODE_SCENE);
this._numSkel = this._resolveSkel(NODE_NUM);
if (!this._sceneSkel || !this._numSkel) return;
this._skinIndex = this._resolveCurrentSkinIndex(this._sceneSkel);
this._playIdle();
this._scheduleNext();
}
/** 停止定时与切换,解除引用 */
unbind(): void {
this._switching = false;
const host = this._host;
if (host?.isValid) {
host.unschedule(this._onInterval);
host.unschedule(this._onSwitchEvent);
host.unschedule(this._onSwitchAnimDone);
}
this._host = null;
this._levelSelect = null;
this._sceneSkel = null;
this._numSkel = null;
}
/** 解析子节点 Spine */
private _resolveSkel(name: string): sp.Skeleton | null {
const root = this._levelSelect;
if (!root?.isValid) return null;
const node = root.getChildByName(name);
if (!node?.isValid) {
console.log('[PanelHomeSceneSwitch] 节点缺失', name);
return null;
}
const skel = node.getComponent(sp.Skeleton);
if (!skel) {
console.log('[PanelHomeSceneSwitch] 缺少 sp.Skeleton', name);
return null;
}
if (skel.skeletonData) {
ensureSpineSkeletonDataRuntime(skel.skeletonData);
}
return skel;
}
/** 读 Inspector defaultSkin 在列表中的下标;未知则 0 */
private _resolveCurrentSkinIndex(skel: sp.Skeleton): number {
const cur = String((skel as sp.Skeleton & { defaultSkin?: string }).defaultSkin ?? '').trim();
for (let i = 0; i < SCENE_SKINS.length; i++) {
if (SCENE_SKINS[i] === cur) return i;
}
console.log('[PanelHomeSceneSwitch] 当前皮肤不在列表,按初始处理', cur);
return 0;
}
/** 数字播待机循环 */
private _playIdle(): void {
const num = this._numSkel;
if (!num?.isValid) return;
if (num.skeletonData) ensureSpineSkeletonDataRuntime(num.skeletonData);
if (!num.findAnimation(ANIM_IDLE)) {
console.log('[PanelHomeSceneSwitch] 缺少动画', ANIM_IDLE);
return;
}
num.setAnimation(0, ANIM_IDLE, true);
}
/** 随机 3~5 秒后再切 */
private _scheduleNext(): void {
const host = this._host;
if (!host?.isValid) return;
host.unschedule(this._onInterval);
const delay = INTERVAL_MIN + Math.random() * (INTERVAL_MAX - INTERVAL_MIN);
host.scheduleOnce(this._onInterval, delay);
}
/** 读动画事件时刻(秒);无则 null */
private _findAnimEventTime(skel: sp.Skeleton, animName: string, eventName: string): number | null {
if (!skel?.isValid || !animName || !eventName) return null;
const sd = skel.skeletonData as sp.SkeletonData & { skeletonJson?: unknown; _skeletonJson?: unknown };
if (!sd?.isValid) return null;
try {
ensureSpineSkeletonDataRuntime(sd);
const raw = (sd.skeletonJson ?? sd._skeletonJson) as {
animations?: Record<string, { events?: Array<{ time?: number; name?: string }> }>;
} | null;
const events = raw?.animations?.[animName]?.events;
if (!events || events.length === 0) return null;
for (let i = 0; i < events.length; i++) {
const e = events[i];
if (String(e?.name ?? '') !== eventName) continue;
const t = Number(e.time);
if (!Number.isFinite(t) || t < 0) continue;
return t;
}
return null;
} catch {
return null;
}
}
/** 间隔到:播场景变换,事件帧切皮,播完回待机 */
private _onInterval = (): void => {
if (this._switching) return;
const num = this._numSkel;
const host = this._host;
if (!num?.isValid || !host?.isValid) return;
if (num.skeletonData) ensureSpineSkeletonDataRuntime(num.skeletonData);
const anim = num.findAnimation(ANIM_SWITCH);
if (!anim) {
console.log('[PanelHomeSceneSwitch] 缺少动画', ANIM_SWITCH);
this._scheduleNext();
return;
}
const eventTime = this._findAnimEventTime(num, ANIM_SWITCH, EVENT_SWITCH);
if (eventTime == null) {
console.log('[PanelHomeSceneSwitch] 缺少事件帧', EVENT_SWITCH, ANIM_SWITCH);
this._scheduleNext();
return;
}
const duration = Number(anim.duration);
const scale = Math.max(0.01, num.timeScale || 1);
const eventDelay = eventTime / scale;
const doneDelay = (Number.isFinite(duration) && duration > 0 ? duration : eventTime) / scale;
this._switching = true;
num.setAnimation(0, ANIM_SWITCH, false);
host.unschedule(this._onSwitchEvent);
host.unschedule(this._onSwitchAnimDone);
host.scheduleOnce(this._onSwitchEvent, eventDelay);
host.scheduleOnce(this._onSwitchAnimDone, Math.max(eventDelay, doneDelay));
};
/** 事件帧:随机其他皮肤 */
private _onSwitchEvent = (): void => {
if (!this._switching) return;
const scene = this._sceneSkel;
if (!scene?.isValid) return;
const next = this._pickOtherSkinIndex(this._skinIndex);
const skin = SCENE_SKINS[next];
if (scene.skeletonData) ensureSpineSkeletonDataRuntime(scene.skeletonData);
try {
(scene as sp.Skeleton & { defaultSkin: string }).defaultSkin = skin;
scene.setSkin(skin);
scene.setSlotsToSetupPose();
this._skinIndex = next;
} catch (err) {
console.log('[PanelHomeSceneSwitch] setSkin 失败', skin, err);
}
};
/** 变换播完:回待机并排下次 */
private _onSwitchAnimDone = (): void => {
if (!this._switching) return;
this._switching = false;
this._playIdle();
this._scheduleNext();
};
/** 从当前以外的 3 个皮肤中随机下标 */
private _pickOtherSkinIndex(cur: number): number {
const others: number[] = [];
for (let i = 0; i < SCENE_SKINS.length; i++) {
if (i !== cur) others.push(i);
}
if (others.length === 0) return cur;
return others[Math.floor(Math.random() * others.length)];
}
}