Files
RedPacketRain/assets/app-bundle/app-view/page/main/native/PageMain.ts

642 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { _decorator, Asset, BoxCollider2D, Contact2DType, EventTouch, Game, game, ImageAsset, Input, input, KeyCode, Node, NodeEventType, Skeleton, sp, Sprite, SpriteFrame, Texture2D, tween, UIOpacity, UITransform, Vec3 } from 'cc';
import BaseView from '../../../../../../extensions/app/assets/base/BaseView';
import { Tools } from 'db://assets/res-native/tools/Tools';
import { app } from 'db://assets/app/app';
const { ccclass, property } = _decorator;
/** 游戏时间 */
const GAME_TIME = 30;
/** 游戏边界 */
const MOVE_BOUNDARY = 500;
@ccclass('PageMain')
export class PageMain extends BaseView {
/** 掉落红包预制件 */
@property(Node) redPacket: Node = null!;
/** 时间 */
@property(Node) lab_time: Node = null!;
/** 分数 */
@property(Node) lab_score: Node = null!;
/** 开始按钮 */
@property(Node) btn_start: Node = null!;
/** 红包天空 */
@property(Node) red_sky: Node = null!;
/** 财神 */
@property(Node) caiShen: Node = null!;
/** 财神上位节点,用来防止文字旋转 */
@property(Node) caiShen_up: Node = null!;
/** 背景 */
@property(Node) background: Node = null!;
/** 需要浮动 */
@property(Node) needFlow: Node[] = [];
/** 需要闪烁 */
@property(Node) needFlash: Node[] = [];
/** 获奖文字 */
@property(Node) lab_add: Node = null!;
/** 准备页面 */
@property(Node) readyPage: Node[] = [];
/** 游戏页面 */
@property(Node) gamePage: Node[] = [];
/** 去充值按钮 */
@property(Node) btn_to_charge: Node = null!;
/** 去游戏按钮 */
@property(Node) btn_to_game: Node = null!;
/** 奖励界面 */
@property(Node) rw: Node = null!;
/** 倒计时 */
@property(Node) count: Node = null!;
/** 音乐按钮 */
@property(Node) btn_music: Node = null!;
/** 关闭音乐按钮 */
@property(Node) btn_no_music: Node = null!;
/** logo展示 */
@property(Sprite) logo: Sprite = null!;
/** 金币雨 */
@property(Node) gold_rain: Node = null!;
/** 是否正在游戏 */
private _gaming = false;
/** 剩余游戏时间 */
private _time = 0;
/** 红包管理器 */
private _redMgr : Node[] = [];
/** 红包掉落间隔时间 秒 */
private _redTime: number = 1/2;
/** 当前游戏id */
private _game_id = "";
/** 当前分数 */
private _score = 0;
/** 最大分数 */
private _maxScore = 0;
/** 剩余红包个数 */
private _redRemain = 0;
/** 财神移动速度 */
private readonly caiShenMoveSpeed: number = 600;
/** 移动边界 */
private readonly leftBoundary: number = -MOVE_BOUNDARY;
private readonly rightBoundary: number = MOVE_BOUNDARY;
/** 当前移动方向 */
private _currentDirection: number = 0; // -1:左, 0:无, 1:右
/** 倒计时 */
private countdownInterval: any;
/** 游戏开始需要的时间和充值 */
private _money_need : boolean = false;
private _time_need : boolean = false;
private _chance_balance : number = 0;
/** 记录切换到后台时间 */
private _hideTime: number = 0;
// 初始化的相关逻辑写在这
onLoad() {}
// 界面打开时的相关逻辑写在这(onShow可被多次调用-它与onHide不成对)
onShow(params: any) {
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
// 注册触摸事件
this.background.on(NodeEventType.TOUCH_MOVE, this.onTouchMove, this);
this.background.on(NodeEventType.TOUCH_START, this.onTouchStart, this);
this.background.on(NodeEventType.TOUCH_END, this.onTouchEnd, this);
// 监听隐藏事件(切换到后台)
game.on(Game.EVENT_HIDE, this.onAppHide, this);
// 监听显示事件(从前台返回)
game.on(Game.EVENT_SHOW, this.onAppShow, this);
// 开始更新循环以处理持续移动
this.schedule(this.updateMovement, 1/60); // 每帧更新
this.startFlow()
this.goToReadyPage()
// Tools.httpGetReq("mini-games/angpau/assets", (res: any) => {
// Tools.remoteLoadSprite(res.logo, this.logo)
// })
}
// 界面关闭时的相关逻辑写在这(已经关闭的界面不会触发onHide)
onHide(result: undefined) {
// 注销键盘事件
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
// 注销触摸事件
this.background.off(NodeEventType.TOUCH_MOVE, this.onTouchMove, this);
this.background.off(NodeEventType.TOUCH_START, this.onTouchStart, this);
this.background.off(NodeEventType.TOUCH_END, this.onTouchEnd, this);
// 移除事件监听
game.off(Game.EVENT_HIDE, this.onAppHide, this);
game.off(Game.EVENT_SHOW, this.onAppShow, this);
// 取消更新循环
this.unschedule(this.updateMovement);
return result;
}
initReadyPage(d:any){
// d.progress.current = 500
// d.event.start_at = "2026-01-22T23:59:59+08:00"
Tools.SetChildText(this.readyPage[0], "lab_title", d.title)
Tools.SetChildText(this.readyPage[0], "lab_act_time", d.footer1.title)
Tools.SetChildText(this.readyPage[0], "lab_act_time/time", d.footer1.desc)
Tools.SetChildText(this.readyPage[0], "lab_act_qua", d.footer2.title)
Tools.SetChildText(this.readyPage[0], "lab_act_qua/time", d.footer2.desc)
Tools.SetChildText(this.readyPage[0], "lab_charge_pro/now", d.progress.current)
Tools.SetChildText(this.readyPage[0], "lab_charge_pro/all", d.progress.max)
Tools.SetChildText(this.readyPage[0], "lab_charge_goal/num", (d.progress.max - d.progress.current).toString())
Tools.ActChild(this.readyPage[0], "btn_save", d.progress.max > d.progress.current)
Tools.ActChild(this.readyPage[0], "btn_start", d.progress.max <= d.progress.current)
Tools.ActChild(this.readyPage[0], "lab_charge", d.progress.max > d.progress.current)
Tools.ActChild(this.readyPage[0], "lab_charge_goal", d.progress.max > d.progress.current)
Tools.ActChild(this.readyPage[0], "lab_get_res", d.progress.max <= d.progress.current)
const fill = Tools.MathClampZeroToOne(d.progress.current / d.progress.max);
const bar = this.readyPage[0].getChildByName("pro_bar")
Tools.GetChildComp(bar, "bar", Sprite).fillRange = fill;
bar.getChildByName("point").setPosition(fill * bar.getComponent(UITransform).width, 0, 0)
this.startCountdown(d.event.start_at)
this._money_need = d.progress.max <= d.progress.current
this._time_need = false
this._chance_balance = d.angpau_chance_balance
}
/**
* 计算倒计时剩余时间
* @param targetDate 目标日期
* @returns 包含天、小时、分钟、秒的对象
*/
calculateCountdown(targetDate: string | Date): { days: number; hours: number; minutes: number; seconds: number } {
const targetTime = new Date(targetDate).getTime();
const currentTime = new Date().getTime();
// 如果目标时间已过,则返回全零
if (targetTime <= currentTime) {
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
}
const diffTime = targetTime - currentTime;
const days = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const hours = Math.floor((diffTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diffTime % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diffTime % (1000 * 60)) / 1000);
return { days, hours, minutes, seconds };
}
/**
* 开始倒计时显示
*/
startCountdown(targetDate:string) {
// 立即更新一次
this.updateCountdownDisplay(targetDate);
// 每秒更新一次倒计时
this.countdownInterval = setInterval(() => {
this.updateCountdownDisplay(targetDate);
}, 1000);
}
/**
* 停止倒计时
*/
stopCountdown() {
if (this.countdownInterval) {
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}
}
/**
* 更新倒计时显示
*/
updateCountdownDisplay(targetDate: string) {
const countdown = this.calculateCountdown(targetDate);
// 更新UI显示
if (this.readyPage && this.readyPage[0]) {
Tools.SetChildText(this.readyPage[0], "lab_time/day", countdown.days.toString());
Tools.SetChildText(this.readyPage[0], "lab_time/hour", countdown.hours.toString());
Tools.SetChildText(this.readyPage[0], "lab_time/minute", countdown.minutes.toString());
Tools.SetChildText(this.readyPage[0], "lab_time/second", countdown.seconds.toString());
}
if (countdown.days == 0 && countdown.hours == 0 && countdown.minutes == 0 && countdown.seconds == 0){
this.stopCountdown()
this._time_need = true
}
}
/** 去游戏界面 */
btnToGamePage(){
if (!this._money_need){
app.manager.ui.showToast("存款金额不够")
return
}
if (!this._time_need){
app.manager.ui.showToast("游戏时间未到")
return
}
if (this._chance_balance <= 0){
app.manager.ui.showToast("抽奖次数已用完")
return
}
for (let i = 0; i < this.readyPage.length; i++) {
this.readyPage[i].active = false;
}
for (let i = 0; i < this.gamePage.length; i++) {
this.gamePage[i].active = true;
}
this.caiShen.getComponent(sp.Skeleton).paused = true
}
/** 去准备页面 */
goToReadyPage(){
for (let i = 0; i < this.gamePage.length; i++) {
this.gamePage[i].active = false;
}
Tools.httpGetReq("mini-games/angpau/event", (res: any) => {
for (let i = 0; i < this.readyPage.length; i++) {
this.readyPage[i].active = true;
}
this.initReadyPage(res)
})
}
btnGameStart(){
this.btn_start.active = false;
this._game_id = "";
Tools.httpReq("mini-games/angpau/reward", {}, (res: any) => {
this.count.active = true;
this._maxScore = res.max_reward;
this._game_id = res.game_id;
this._redRemain = Math.floor(GAME_TIME / this._redTime) - 1;
//开始动画
this.loadSprite("main_count_3", this.count);
app.manager.sound.playEffect({name : "effect/start"});
let countNum = 3;
let fun = () => {
countNum--;
if (countNum <= -1) {
this.unschedule(fun);
this.count.active = false;
this._gaming = true;
this._time = GAME_TIME;
this._score = 0;
this.updateScore();
this.resetRedMgr();
Tools.SetText(this.lab_time, this._time + "s");
this.schedule(this.countTime, 1);
this.schedule(this.dropRedPacket, this._redTime);
return
}
this.loadSprite(`main_count_${countNum}`, this.count);
}
this.schedule(fun, 1);
app.manager.sound.playEffect({name : "effect/start"});
},()=>{
this.btn_start.active = true;
})
}
startFlow(){
for (let i = 0; i < this.needFlow.length; i++) {
this.aniFlow(this.needFlow[i])
}
for (let i = 0; i < this.needFlash.length; i++) {
this.aniFlash(this.needFlash[i])
}
}
aniFlash(node: Node){
let light = Math.random() * 255;
let time = (255 - light) / 100;
let opacity = node.getComponent(UIOpacity);
if (opacity) {
tween(opacity)
.to(time, {opacity: light})
.to(time, {opacity: 255})
.call(() => {
this.aniFlash(node)
})
.start();
}
}
aniFlow(node: Node){
let dis = Math.random() * 20 + 50;
tween(node)
.by(dis / 12, {position: new Vec3(0, -dis, 0)})
.by(dis / 12, {position: new Vec3(0, dis, 0)})
.call(() => {
this.aniFlow(node)
})
.start();
}
resetRedMgr(){
this.desAllRed();
this._redMgr = [];
}
desAllRed(){
// 创建副本以避免在遍历时修改数组
const redPacketsToDestroy = [...this._redMgr];
for (let i = 0; i < redPacketsToDestroy.length; i++) {
if (redPacketsToDestroy[i] && redPacketsToDestroy[i].isValid) {
const collider = redPacketsToDestroy[i].getComponent(BoxCollider2D);
if (collider) {
try {
collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
} catch (error) {
console.warn('移除碰撞监听器时出错:', error);
}
}
// 延迟销毁
this.scheduleOnce(() => {
if (redPacketsToDestroy[i] && redPacketsToDestroy[i].isValid) {
redPacketsToDestroy[i].destroy();
}
}, 0.01);
}
}
// 清空管理器
this._redMgr = [];
}
countTime(){
if (!this._gaming) return;
this._time--;
Tools.SetText(this.lab_time, this._time + "s");
if (this._time <= 0) {
this.gameOver();
}
}
gameOver(){
Tools.SetText(this.lab_time, "0s");
this._gaming = false;
this.caiShen.getComponent(sp.Skeleton).paused = true;
this.btn_start.active = true;
this.unschedule(this.countTime);
this.unschedule(this.dropRedPacket);
this.desAllRed();
Tools.httpReq("mini-games/angpau/finalize", {game_id:this._game_id, final_amount:this._score}, (res: any) => {
Tools.SetChildText(this.rw, "bg/lab_layout/num", res.credited_amount);
Tools.SetChildText(this.rw, "bg/lab_layout/lab_num", res.currency);
this.rw.active = true;
let rain = Tools.AddChild(this.rw, this.gold_rain, "gold_rain");
rain.setPosition(0, 420, 0);
rain.active = true;
this.scheduleOnce(()=>{ rain?.destroy(); }, 5)
app.manager.sound.playEffect({name : "effect/gold_rain"});
})
}
btnCloseRw(){
this.rw.active = false;
}
dropRedPacket(){
let n = this._redMgr.length
this._redMgr[n] = Tools.AddChild(this.red_sky, this.redPacket, "redPacket");
this._redMgr[n].active = true;
this._redMgr[n].setPosition(Math.random() * MOVE_BOUNDARY * 2 - MOVE_BOUNDARY, 0);
const collider = this._redMgr[n].getComponent(BoxCollider2D);
if (collider) {
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
const amounts = (this._maxScore / this._redRemain ).toFixed(1);
this._maxScore -= parseFloat(amounts);
this._redRemain--
//@ts-ignore
this._redMgr[n].amount = amounts; // 存储金额
}
/**
* 碰撞开始时的回调
* @param contact 碰撞信息
* @param selfCollider 自身碰撞器
* @param otherCollider 另一个碰撞器
*/
onBeginContact(selfCollider: any, otherCollider: any) {
if (otherCollider.node.name === 'red_floor') {
this.removeRedPacket(selfCollider.node);
} else if (otherCollider.node.name === 'CaiShen') {
app.manager.sound.playEffect({name : "effect/catch"});
const redPacketNode = selfCollider.node;
const amount = redPacketNode.amount || 0; // 默认为0
this.removeRedPacket(selfCollider.node);
this._score = this._score + parseFloat(amount)
let lab = Tools.AddChild(this.caiShen_up, this.lab_add)
Tools.SetText(lab, "+" + amount)
this.aniScoreUp(lab)
this.updateScore();
if (navigator.vibrate) {
navigator.vibrate(200);
}
}
}
aniScoreUp(node: Node){
node.active = true
node.setPosition(0, 400, 0)
let opacity = node.getComponent(UIOpacity)
tween(opacity)
.to(0.5, {opacity: 0})
.call(() => {
node.destroy()
})
.start();
tween(node)
.by(0.5, {position: new Vec3(0, 50, 0)})
.start();
}
/**
* 更新分数
*/
updateScore() {
let score = this._score == 0 ? "0" : this._score.toFixed(1);
Tools.SetText(this.lab_score, score);
}
/**
* 移除红包
*/
removeRedPacket(redPacketNode: Node) {
// 检查节点是否有效
if (!redPacketNode || redPacketNode.isValid === false) {
return;
}
// 获取碰撞体组件并移除监听器
const collider = redPacketNode.getComponent(BoxCollider2D);
if (collider) {
try {
collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
} catch (error) {
console.warn('移除碰撞监听器时出错:', error);
}
}
//从管理器中移除
const index = this._redMgr.findIndex(item => item === redPacketNode);
if (index > -1) {
this._redMgr.splice(index, 1);
}
this.scheduleOnce(()=>{
tween(redPacketNode.getComponent(UIOpacity))
.to(0.1, { opacity: 0 })
.call(() => {
try {
redPacketNode.destroy();
} catch (error) {
console.warn('销毁节点时出错:', error);
}
})
.start();
}, 0)
}
/**
* 键盘按下处理
*/
private onKeyDown(event: any) {
if (!this._gaming) return
this.caiShen.getComponent(sp.Skeleton).paused = false
switch (event.keyCode) {
case KeyCode.ARROW_LEFT:
case KeyCode.KEY_A:
this._currentDirection = -1;
this.caiShen.setScale(new Vec3(-1, 1, 1))
break;
case KeyCode.ARROW_RIGHT:
case KeyCode.KEY_D:
this._currentDirection = 1;
this.caiShen.setScale(new Vec3(1, 1, 1))
break;
}
}
/**
* 更新财神移动
*/
private updateMovement(dt: number) {
if (this._currentDirection === 0 || !this._gaming) {
return;
}
const currentPosition = this.caiShen_up.position;
let newX = currentPosition.x + this._currentDirection * this.caiShenMoveSpeed * dt;
// 边界限制
newX = Math.max(this.leftBoundary, Math.min(this.rightBoundary, newX));
this.caiShen_up.setPosition(newX, currentPosition.y, currentPosition.z);
}
/**
* 键盘释放处理
*/
private onKeyUp(event: any) {
switch (event.keyCode) {
case KeyCode.ARROW_LEFT:
case KeyCode.KEY_A:
if (this._currentDirection === -1) {
this._currentDirection = 0;
this.caiShen.getComponent(sp.Skeleton).paused = true
}
break;
case KeyCode.ARROW_RIGHT:
case KeyCode.KEY_D:
if (this._currentDirection === 1) {
this._currentDirection = 0;
this.caiShen.getComponent(sp.Skeleton).paused = true
}
break;
}
}
/**
* 触摸移动处理
*/
private onTouchMove(touch: EventTouch) {
if (!this._gaming) return
const delta = touch.getDelta();
const currentPosition = this.caiShen_up.position;
let newX = currentPosition.x + delta.x;
this.caiShen.setScale(new Vec3(delta.x > 0 ? 1 : -1, 1, 1))
// 边界限制
newX = Math.max(this.leftBoundary, Math.min(this.rightBoundary, newX));
this.caiShen_up.setPosition(newX, currentPosition.y, currentPosition.z);
}
private onTouchStart(touch: EventTouch) {
if (!this._gaming) return
this.caiShen.getComponent(sp.Skeleton).paused = false;
}
private onTouchEnd(touch: EventTouch) {
if (!this._gaming) return
this.caiShen.getComponent(sp.Skeleton).paused = true;
}
loadSprite(pic: string, node: Node){
this.loadRes(pic, Asset, (res: ImageAsset)=>{
let sp = new SpriteFrame()
let tex = new Texture2D();
tex.image = res;
sp.texture = tex
node.getComponent(Sprite).spriteFrame = sp
});
}
// 应用隐藏时的处理(切换到其他应用)
private onAppHide() {
// 暂停游戏逻辑
this._hideTime = Date.now();
}
// 应用显示时的处理(返回应用)
private onAppShow() {
// 计算在后台停留的时间(单位:毫秒)
const currentTime = Date.now();
const timeInBackground = currentTime - this._hideTime;
const timeInBackgroundSeconds = timeInBackground / 1000; // 转换为秒
// 如果在后台停留时间超过10秒结束游戏
if (this._gaming && timeInBackgroundSeconds > 10) {
app.manager.ui.showToast("离开时间过长,游戏已结束");
this.gameOver();
}
}
closeMusic(){
this.btn_music.active = false
this.btn_no_music.active = true
app.manager.sound.stopMusic()
}
openMusic(){
this.btn_music.active = true
this.btn_no_music.active = false
app.manager.sound.playDefaultMusic()
}
}