feat: 接入激励视频
This commit is contained in:
600
软著源代码/04-首页与体力系统.md
Normal file
600
软著源代码/04-首页与体力系统.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# 梗中作乐 V1.0 - 源代码文档(第 4 部分)
|
||||
|
||||
软件名称:梗中作乐
|
||||
版本号:V1.0
|
||||
|
||||
---
|
||||
|
||||
## 第 4 部分:首页与体力系统
|
||||
|
||||
本部分包含小游戏首页、体力(次数)管理、背景自适应缩放三个模块。
|
||||
|
||||
### 4.1 首页 (assets/prefabs/PageHome.ts)
|
||||
|
||||
```typescript
|
||||
import { _decorator, Node, Button, Label, tween, Vec3, UIOpacity, UITransform, Color, instantiate, ProgressBar } from 'cc';
|
||||
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||
import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
|
||||
import { StaminaManager } from 'db://assets/scripts/utils/StaminaManager';
|
||||
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
|
||||
import { StaminaInfo } from 'db://assets/scripts/types/ApiTypes';
|
||||
import { AuthManager } from 'db://assets/scripts/utils/AuthManager';
|
||||
import { AchievementTitleManager } from 'db://assets/scripts/utils/AchievementTitleManager';
|
||||
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
|
||||
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* 首页组件
|
||||
* 继承 BaseView,实现页面生命周期
|
||||
*/
|
||||
@ccclass('PageHome')
|
||||
export class PageHome extends BaseView {
|
||||
/** 默认体力上限 */
|
||||
private static readonly DEFAULT_STAMINA_MAX = 50;
|
||||
|
||||
@property({ type: Node, tooltip: '开始游戏按钮' })
|
||||
startGameBtn: Node | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: 'PK按钮' })
|
||||
pkBtn: Node | null = null;
|
||||
|
||||
/** 体力值显示标签 */
|
||||
@property(Label)
|
||||
liveLabel: Label | null = null;
|
||||
|
||||
/** 首页主称号文本 */
|
||||
@property(Label)
|
||||
levelLabel: Label | null = null;
|
||||
|
||||
/** 称号进度条 */
|
||||
@property(ProgressBar)
|
||||
titleProgressBar: ProgressBar | null = null;
|
||||
|
||||
/** 称号进度提示文案 */
|
||||
@property(Label)
|
||||
progressLabel: Label | null = null;
|
||||
|
||||
/** 称号进度游标 */
|
||||
@property(Node)
|
||||
progressAnchor: Node | null = null;
|
||||
|
||||
/** 飞行动画持续时间(秒) */
|
||||
private static readonly FLY_DURATION = 0.5;
|
||||
|
||||
/** 到达后弹跳持续时间(秒) */
|
||||
private static readonly BOUNCE_DURATION = 0.15;
|
||||
|
||||
/** 浮动文本动画持续时间(秒) */
|
||||
private static readonly FLOAT_DURATION = 0.8;
|
||||
|
||||
/** 浮动文本上移距离 */
|
||||
private static readonly FLOAT_OFFSET_Y = 120;
|
||||
|
||||
/** 是否正在播放体力消耗动画 */
|
||||
private _isAnimating: boolean = false;
|
||||
|
||||
/** 进度游标 0% 时的本地 X 坐标,根据 ProgressBar Bar 子节点的左端推导出来 */
|
||||
private _progressAnchorStartX: number | null = null;
|
||||
|
||||
/**
|
||||
* 页面首次加载时调用
|
||||
*/
|
||||
onViewLoad(): void {
|
||||
console.log('[PageHome] onViewLoad');
|
||||
this._cacheProgressAnchorStartX();
|
||||
this.updateAchievementTitleInfo();
|
||||
this._initButtons();
|
||||
this._initWxShare();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化微信分享功能
|
||||
*/
|
||||
private _initWxShare(): void {
|
||||
WxSDK.initShare({
|
||||
title: '写英语',
|
||||
imageUrl: '',
|
||||
query: ''
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化按钮事件
|
||||
*/
|
||||
private _initButtons(): void {
|
||||
if (this.startGameBtn) {
|
||||
this.startGameBtn.on(Button.EventType.CLICK, this._onStartGameClick, this);
|
||||
}
|
||||
if (this.pkBtn) {
|
||||
this.pkBtn.on(Button.EventType.CLICK, this._onPkClick, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始游戏按钮点击回调
|
||||
*/
|
||||
private _onStartGameClick(): void {
|
||||
if (this._isAnimating) return;
|
||||
|
||||
AudioManager.instance.playButtonClick();
|
||||
console.log('[PageHome] 开始游戏按钮点击');
|
||||
|
||||
// 体力检查
|
||||
if (!StaminaManager.instance.hasStamina()) {
|
||||
ToastManager.show('体力不足,请等待恢复');
|
||||
return;
|
||||
}
|
||||
|
||||
// 兜底:清空可能残留的好友分享挑战状态,确保进入的是纯主线挑战
|
||||
// 场景:用户从微信好友分享卡片进入挑战 → 退出回首页 → 再次点击开始游戏
|
||||
// 若不清理,缓存的 PageLevel 仍会读到 ShareManager 的分享态数据
|
||||
if (ShareManager.instance.isShareMode) {
|
||||
console.log('[PageHome] 检测到残留的分享挑战状态,清理后再进入主线挑战');
|
||||
ShareManager.instance.clearShareMode();
|
||||
}
|
||||
|
||||
this._isAnimating = true;
|
||||
this._playStaminaCostAnimation()
|
||||
.then(() => {
|
||||
ViewManager.instance.open('PageLevel', { params: { shareMode: false } });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('[PageHome] 体力消耗动画异常:', err);
|
||||
// 异常兜底:直接进入关卡
|
||||
ViewManager.instance.open('PageLevel', { params: { shareMode: false } });
|
||||
})
|
||||
.finally(() => {
|
||||
this._isAnimating = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PK按钮点击回调
|
||||
*/
|
||||
private _onPkClick(): void {
|
||||
AudioManager.instance.playButtonClick();
|
||||
console.log('[PageHome] PK按钮点击');
|
||||
ViewManager.instance.open('PageWriteLevels');
|
||||
}
|
||||
|
||||
// ========== 体力消耗动画 ==========
|
||||
|
||||
/**
|
||||
* 通过节点路径查找 IconLive
|
||||
*/
|
||||
private _findIconLive(): Node | null {
|
||||
return this.node
|
||||
.getChildByName('TopLayout')
|
||||
?.getChildByName('Live')
|
||||
?.getChildByName('IconLive') ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将节点的世界坐标转换为目标父节点的本地坐标
|
||||
*/
|
||||
private _worldToLocal(worldPos: Vec3, parent: Node): Vec3 {
|
||||
const parentTransform = parent.getComponent(UITransform);
|
||||
if (!parentTransform) return worldPos;
|
||||
return parentTransform.convertToNodeSpaceAR(worldPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的世界坐标
|
||||
*/
|
||||
private _getWorldPos(node: Node): Vec3 {
|
||||
const transform = node.getComponent(UITransform);
|
||||
if (!transform) return node.worldPosition.clone();
|
||||
return transform.convertToWorldSpaceAR(Vec3.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放体力消耗动画
|
||||
* 1. 克隆 IconLive 飞向 StarGame 按钮
|
||||
* 2. 到达后弹跳
|
||||
* 3. "体力值-1" 浮动文本上移渐隐
|
||||
* 4. 同步更新体力数字
|
||||
*/
|
||||
private _playStaminaCostAnimation(): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
const iconLive = this._findIconLive();
|
||||
const targetBtn = this.startGameBtn;
|
||||
|
||||
if (!iconLive || !targetBtn) {
|
||||
console.warn('[PageHome] 动画节点未找到,跳过动画');
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 坐标计算 ---
|
||||
const iconWorldPos = this._getWorldPos(iconLive);
|
||||
const targetWorldPos = this._getWorldPos(targetBtn);
|
||||
const rootNode = this.node;
|
||||
|
||||
// 起始和终点在 root 本地空间的坐标
|
||||
const startLocal = this._worldToLocal(iconWorldPos, rootNode);
|
||||
const endLocal = this._worldToLocal(targetWorldPos, rootNode);
|
||||
|
||||
// --- 克隆飞行节点 ---
|
||||
const flyNode = instantiate(iconLive);
|
||||
flyNode.name = '_flyIcon';
|
||||
flyNode.setPosition(startLocal);
|
||||
// 保持与原始 IconLive 相同的缩放
|
||||
flyNode.setScale(iconLive.worldScale.clone());
|
||||
rootNode.addChild(flyNode);
|
||||
|
||||
// 隐藏原始 IconLive
|
||||
iconLive.active = false;
|
||||
|
||||
// --- 飞行路径(带弧度的抛物线效果) ---
|
||||
// 中间控制点:x 取中点,y 取较高值 + 偏移形成弧线
|
||||
const midX = (startLocal.x + endLocal.x) / 2;
|
||||
const midY = Math.max(startLocal.y, endLocal.y) + 150;
|
||||
const midPoint = new Vec3(midX, midY, 0);
|
||||
|
||||
// 阶段1:飞到弧线顶点
|
||||
const halfDuration = PageHome.FLY_DURATION / 2;
|
||||
|
||||
tween(flyNode)
|
||||
.to(halfDuration, { position: midPoint }, { easing: 'quadOut' })
|
||||
.to(halfDuration, { position: endLocal }, { easing: 'quadIn' })
|
||||
// 阶段2:到达弹跳
|
||||
.to(PageHome.BOUNCE_DURATION / 2, { scale: new Vec3(0.4, 0.4, 1) }, { easing: 'quadOut' })
|
||||
.to(PageHome.BOUNCE_DURATION / 2, { scale: new Vec3(0.3, 0.3, 1) }, { easing: 'quadIn' })
|
||||
.call(() => {
|
||||
// 飞行完成 — flyNode 使命结束,立即清理
|
||||
flyNode.destroy();
|
||||
iconLive.active = true;
|
||||
|
||||
// 创建浮动文本
|
||||
this._showFloatText(targetBtn, rootNode);
|
||||
|
||||
// 乐观更新体力数字
|
||||
this._optimisticUpdateStamina();
|
||||
|
||||
// 等浮动文本播完再 resolve(用 setTimeout,不依赖已销毁节点的 tween)
|
||||
setTimeout(() => resolve(), PageHome.FLOAT_DURATION * 1000);
|
||||
})
|
||||
.start();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示浮动提示文本 "体力值-1"
|
||||
* 从按钮位置向上漂移并渐隐
|
||||
*/
|
||||
private _showFloatText(anchorNode: Node, parentNode: Node): void {
|
||||
// 创建文本节点
|
||||
const textNode = new Node('_floatText');
|
||||
textNode.addComponent(UITransform);
|
||||
|
||||
const label = textNode.addComponent(Label);
|
||||
label.string = '体力值-1';
|
||||
label.fontSize = 36;
|
||||
label.lineHeight = 40;
|
||||
label.color = new Color(255, 80, 80, 255);
|
||||
label.isBold = true;
|
||||
|
||||
// 复用 liveLabel 的字体(如果有)
|
||||
if (this.liveLabel?.font) {
|
||||
label.font = this.liveLabel.font;
|
||||
}
|
||||
|
||||
// 添加 UIOpacity 用于渐隐
|
||||
const opacity = textNode.addComponent(UIOpacity);
|
||||
opacity.opacity = 255;
|
||||
|
||||
// 定位到按钮上方
|
||||
const anchorWorldPos = this._getWorldPos(anchorNode);
|
||||
const localPos = this._worldToLocal(anchorWorldPos, parentNode);
|
||||
// 起始位置在按钮上方偏移
|
||||
localPos.y += 120;
|
||||
textNode.setPosition(localPos);
|
||||
|
||||
parentNode.addChild(textNode);
|
||||
|
||||
// 向上漂移 + 渐隐
|
||||
const floatTarget = new Vec3(localPos.x, localPos.y + PageHome.FLOAT_OFFSET_Y, 0);
|
||||
tween(textNode)
|
||||
.to(PageHome.FLOAT_DURATION, { position: floatTarget }, { easing: 'quadOut' })
|
||||
.call(() => {
|
||||
textNode.destroy();
|
||||
})
|
||||
.start();
|
||||
|
||||
tween(opacity)
|
||||
.delay(PageHome.FLOAT_DURATION * 0.3)
|
||||
.to(PageHome.FLOAT_DURATION * 0.7, { opacity: 0 }, { easing: 'quadIn' })
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐观更新体力标签(本地 -1 预扣显示)
|
||||
*/
|
||||
private _optimisticUpdateStamina(): void {
|
||||
if (!this.liveLabel) return;
|
||||
|
||||
const stamina = StaminaManager.instance.getStamina();
|
||||
const maxStamina = this._getStaminaMax(stamina);
|
||||
const displayCurrent = Math.max(0, stamina.current - 1);
|
||||
this.liveLabel.string = `${displayCurrent}/${maxStamina}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面每次显示时调用
|
||||
*/
|
||||
onViewShow(): void {
|
||||
console.log('[PageHome] onViewShow');
|
||||
this.updateStaminaLabel();
|
||||
this.updateAchievementTitleInfo();
|
||||
|
||||
// 保险恢复:防止动画中途被打断导致 IconLive 隐藏残留
|
||||
const iconLive = this._findIconLive();
|
||||
if (iconLive && !iconLive.active) {
|
||||
iconLive.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取体力上限
|
||||
*/
|
||||
private _getStaminaMax(stamina: StaminaInfo): number {
|
||||
return typeof stamina.max === 'number' ? stamina.max : PageHome.DEFAULT_STAMINA_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新体力值显示
|
||||
*/
|
||||
private updateStaminaLabel(): void {
|
||||
if (this.liveLabel) {
|
||||
const stamina = StaminaManager.instance.getStamina();
|
||||
const maxStamina = this._getStaminaMax(stamina);
|
||||
this.liveLabel.string = `${stamina.current}/${maxStamina}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新首页称号进度区域
|
||||
*/
|
||||
private updateAchievementTitleInfo(): void {
|
||||
const titleInfo = AchievementTitleManager.getTitleInfo(AuthManager.instance.completedLevelCount);
|
||||
const progress = this._normalizeProgress(titleInfo.nextTitleProgress);
|
||||
|
||||
if (this.levelLabel) {
|
||||
this.levelLabel.string = titleInfo.titleText;
|
||||
}
|
||||
|
||||
if (this.titleProgressBar) {
|
||||
this.titleProgressBar.progress = progress;
|
||||
}
|
||||
|
||||
if (this.progressLabel) {
|
||||
this.progressLabel.string = titleInfo.progressText;
|
||||
}
|
||||
|
||||
this._updateProgressAnchor(progress);
|
||||
}
|
||||
|
||||
private _cacheProgressAnchorStartX(): void {
|
||||
if (this._progressAnchorStartX !== null || !this.titleProgressBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
const barSprite = this.titleProgressBar.barSprite;
|
||||
if (!barSprite) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bar 节点 anchor 为 (0, 0.5),其本地 position.x 即为进度条可视左端。
|
||||
// ProgressBar 与 ProgressAnchor 共享同一父节点(TitleLevel),
|
||||
// 因此把 Bar 的本地 X 按 ProgressBar 自身的位移与缩放映射到父节点空间,
|
||||
// 才是真正的「0% 起点」。直接拿 progressAnchor.position.x 当起点会导致
|
||||
// 气泡始终被 prefab 摆放偏移量带跑(实测偏右 ~24px)。
|
||||
const progressBarNode = this.titleProgressBar.node;
|
||||
const barLocalX = barSprite.node.position.x;
|
||||
this._progressAnchorStartX = progressBarNode.position.x + barLocalX * progressBarNode.scale.x - 30;
|
||||
}
|
||||
|
||||
private _updateProgressAnchor(progress: number): void {
|
||||
if (!this.progressAnchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._cacheProgressAnchorStartX();
|
||||
|
||||
const startX = this._progressAnchorStartX ?? this.progressAnchor.position.x;
|
||||
const travelWidth = this._getProgressAnchorTravelWidth();
|
||||
this.progressAnchor.setPosition(startX + travelWidth * progress, this.progressAnchor.position.y, this.progressAnchor.position.z);
|
||||
|
||||
const percentLabel = this.progressAnchor.getChildByName('Label')?.getComponent(Label);
|
||||
if (percentLabel) {
|
||||
percentLabel.string = `${Math.round(progress * 100)}%`;
|
||||
}
|
||||
}
|
||||
|
||||
private _getProgressAnchorTravelWidth(): number {
|
||||
if (!this.titleProgressBar) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.abs(this.titleProgressBar.totalLength * this.titleProgressBar.node.scale.x);
|
||||
}
|
||||
|
||||
private _normalizeProgress(progress: number): void {
|
||||
if (!Number.isFinite(progress) || progress <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.min(1, progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面隐藏时调用
|
||||
*/
|
||||
onViewHide(): void {
|
||||
console.log('[PageHome] onViewHide');
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面销毁时调用
|
||||
*/
|
||||
onViewDestroy(): void {
|
||||
console.log('[PageHome] onViewDestroy');
|
||||
// 移除按钮事件监听
|
||||
if (this.startGameBtn) {
|
||||
this.startGameBtn.off(Button.EventType.CLICK, this._onStartGameClick, this);
|
||||
}
|
||||
if (this.pkBtn) {
|
||||
this.pkBtn.off(Button.EventType.CLICK, this._onPkClick, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 体力管理器 (assets/scripts/utils/StaminaManager.ts)
|
||||
|
||||
```typescript
|
||||
import { HttpUtil } from './HttpUtil';
|
||||
import { StorageManager } from './StorageManager';
|
||||
import { AuthManager } from './AuthManager';
|
||||
import { API_TIMEOUT, getLevelEnterUrl, getLevelCompleteUrl } from '../config/ApiConfig';
|
||||
import { ApiEnvelope, StaminaInfo, EnterLevelData, CompleteLevelData } from '../types/ApiTypes';
|
||||
|
||||
/**
|
||||
* 体力值管理器
|
||||
* 单例模式,负责体力值的服务端同步、进入关卡和通关上报
|
||||
* 以服务端为准,本地 StorageManager 作为缓存
|
||||
*/
|
||||
export class StaminaManager {
|
||||
private static _instance: StaminaManager | null = null;
|
||||
|
||||
static get instance(): StaminaManager {
|
||||
if (!this._instance) {
|
||||
this._instance = new StaminaManager();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 获取当前体力信息(从本地缓存)
|
||||
*/
|
||||
getStamina(): StaminaInfo {
|
||||
return StorageManager.getStamina();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新本地缓存的体力信息
|
||||
* @param stamina 服务端返回的体力信息
|
||||
*/
|
||||
updateStamina(stamina: StaminaInfo): void {
|
||||
StorageManager.setStamina(stamina);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前是否有足够的体力
|
||||
*/
|
||||
hasStamina(): boolean {
|
||||
return StorageManager.hasStamina();
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入关卡
|
||||
* 消耗 1 点体力(未通关关卡),获取完整关卡详情(含答案和线索)
|
||||
* @param levelId 关卡 ID
|
||||
* @returns 关卡详情,失败时返回 null
|
||||
*/
|
||||
async enterLevel(levelId: string): Promise<EnterLevelData | null> {
|
||||
if (!AuthManager.instance.isLoggedIn) {
|
||||
console.warn('[StaminaManager] 未登录,无法进入关卡');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await HttpUtil.post<ApiEnvelope<EnterLevelData>>(
|
||||
getLevelEnterUrl(levelId),
|
||||
{},
|
||||
API_TIMEOUT.DEFAULT
|
||||
);
|
||||
|
||||
if (response.success && response.data) {
|
||||
StorageManager.setStamina(response.data.stamina);
|
||||
console.log(`[StaminaManager] 进入关卡 ${levelId},体力: ${response.data.stamina.current}/${response.data.stamina.max}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
console.warn('[StaminaManager] 进入关卡失败:', response.message);
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error('[StaminaManager] 进入关卡请求失败:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通关上报
|
||||
* @param levelId 关卡 ID
|
||||
* @param timeSpent 通关耗时(秒)
|
||||
* @returns 通关响应,失败时返回 null
|
||||
*/
|
||||
async completeLevel(levelId: string, timeSpent: number): Promise<CompleteLevelData | null> {
|
||||
if (!AuthManager.instance.isLoggedIn) {
|
||||
console.warn('[StaminaManager] 未登录,无法上报通关');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await HttpUtil.post<ApiEnvelope<CompleteLevelData>>(
|
||||
getLevelCompleteUrl(levelId),
|
||||
{ timeSpent },
|
||||
API_TIMEOUT.DEFAULT
|
||||
);
|
||||
|
||||
if (response.success && response.data) {
|
||||
console.log(`[StaminaManager] 通关上报成功: ${levelId}, 首次: ${response.data.firstClear}, 耗时: ${response.data.timeSpent}s`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
console.warn('[StaminaManager] 通关上报失败:', response.message);
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error('[StaminaManager] 通关上报请求失败:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 背景缩放工具 (assets/scripts/utils/BackgroundScaler.ts)
|
||||
|
||||
```typescript
|
||||
import { _decorator, Component, UITransform, view } from 'cc';
|
||||
const { ccclass, menu, disallowMultiple } = _decorator;
|
||||
|
||||
@ccclass('BackgroundScaler')
|
||||
@menu('Nexux/BackgroundScaler')
|
||||
@disallowMultiple()
|
||||
export class BackgroundScaler extends Component {
|
||||
onLoad() {
|
||||
const bg = this.node;
|
||||
const uiTransform = bg.getComponent(UITransform);
|
||||
const bgSize = uiTransform?.contentSize;
|
||||
|
||||
if (!bgSize) {
|
||||
console.error('BackgroundScaler: bgSize is null');
|
||||
return;
|
||||
}
|
||||
|
||||
const winSize = view.getVisibleSize();
|
||||
|
||||
const scaleX = winSize.width / bgSize.width;
|
||||
const scaleY = winSize.height / bgSize.height;
|
||||
|
||||
const scale = Math.max(scaleX, scaleY);
|
||||
|
||||
bg.setScale(scale, scale);
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user