Files
Monopoly/assets/app-bundle/app-view/page/main/native/PageMain.ts
2026-03-30 09:39:59 +08:00

595 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, Node, tween, Vec3, Sprite, Asset, ImageAsset, SpriteFrame, Texture2D, Label, UIOpacity, Animation, math, VideoPlayer, UITransform, VideoClip } from 'cc';
import BaseView from '../../../../../../extensions/app/assets/base/BaseView';
import { PageTips } from '../../tips/native/PageTips';
import { app } from 'db://assets/app/app';
import { Tools } from 'db://assets/res-native/tools/Tools';
import { PageRewardhistory } from '../../rewardhistory/native/PageRewardhistory';
import { USERDATA } from 'db://assets/res-native/data/UserData';
import { LANG_ENUM, LANG_SET, LangType } from 'db://assets/res-native/setting/LangSet';
const { ccclass, property } = _decorator;
/** 移动方向 */
const enum M_DIR {
CLOCKWISE = 0, /** 顺时针 */
COUNTER_CLOCKWISE = 1, /** 逆时针 */
}
@ccclass('PageMain')
export class PageMain extends BaseView {
/** 声音按钮 */
@property(Node) btn_music: Node = null!;
/** 当前余额 */
@property(Label) lab_gold: Label = null!;
/** 姓名 */
@property(Label) lab_name: Label = null!;
/** 当前剩余抽奖次数 */
@property(Label) remain: Label = null!;
/** 方向选择 */
@property(Node) n_dir: Node[] = [];
/** 标签 */
@property(Node) sign: Node = null!;
/** 方块 */
@property(Node) blocks: Node = null!;
/** 骰子 */
@property(Node) dices: Node[] = [];
/** 中奖 */
@property(Node) rw: Node = null!;
/** 未中奖 */
@property(Node) norw: Node = null!;
/** 购买次数按钮 */
@property(Node) btns_buy: Node = null!;
/** 开始游戏按钮 */
@property(Node) btn_play: Node = null!;
/** 开始游戏按钮 */
@property(Node) btn_auto: Node = null!;
/** 胜利数字 */
@property(Node) lab_add: Node = null!;
/** 失败数字 */
@property(Node) lab_sub: Node = null!;
/** 分数标签 */
@property(Node) lab_point: Node = null!;
/** 金币雨 */
@property(Node) gold_rain: Node = null!;
/** 背景视频 */
@property(VideoPlayer) video_bg: VideoPlayer = null!;
/** 视频背景图片 */
@property(Sprite) video_sprite: Sprite = null!;
/** 骰子动画 */
@property(Node) ani_dice: Node = null!;
/** 骰子总点数 */
@property(Node) dice_all: Node = null!;
/** 失败视频资源 */
@property(VideoClip) video_win: VideoClip = null!;
@property(VideoClip) video_lose: VideoClip = null!;
@property(VideoClip) video_normal: VideoClip = null!;
/** 方块移动方向 */
private _move_dir: M_DIR = M_DIR.CLOCKWISE;
/** 是否正在游戏 */
private _is_playing = false;
/** 骰子数据 */
private _game_data: any = null;
/** 是否正在连续 */
private _is_continuous : boolean = true
/** 方块顺时针顺序,从顶端开始 */
private _b_sequence : string[] = [
"20","27","24","10","5","15","8","22","30","23","16","12","13","7","17","9","21","26","6","29","19","11","25","14","28","18"
];
/** 标签位置 */
private _sign_position: Vec3[] =[
new Vec3(11, 491.7, 0),
new Vec3(178.2, 407, 0),
new Vec3(323.4, 310.2, 0),
new Vec3(456.5, 227.7, 0),
new Vec3(341, 143, 0),
new Vec3(233.2, 63.8, 0),
new Vec3(121, 0, 0),
new Vec3(231, -77, 0),
new Vec3(341, -143, 0),
new Vec3(467.5, -214.5, 0),
new Vec3(341, -321.2, 0),
new Vec3(231, -401.5, 0),
new Vec3(116.6, -473, 0),
new Vec3(0, -550, 0),
new Vec3(-117.7, -484, 0),
new Vec3(-234.3, -407, 0),
new Vec3(-344.3, -344.3, 0),
new Vec3(-465.3, -242, 0),
new Vec3(-336.6, -159.5, 0),
new Vec3(-226.6, -92.4, 0),
new Vec3(-115.5, -18.7, 0),
new Vec3(-227.7, 48.4, 0),
new Vec3(-341, 121, 0),
new Vec3(-451, 201.3, 0),
new Vec3(-301.4, 298.1, 0),
new Vec3(-159.5, 402.6, 0),
]
private _video:HTMLVideoElement = null
private _canvas:HTMLCanvasElement = null
private _ctx:CanvasRenderingContext2D = null
// 在类中新增变量用于缓存旧资源
private _currentSpriteFrame: SpriteFrame | null = null;
private _isUpdating = false;
// 初始化的相关逻辑写在这
onLoad() {}
// 界面打开时的相关逻辑写在这(onShow可被多次调用-它与onHide不成对)
onShow(params: any) {
this.initUserData()
this.swichBuyAndPlay()
this.initDir()
this.getRwData()
this.onClickTips()
this.bindVideoEvents()
this._canvas = document.createElement('canvas');
this._canvas.width = this.video_sprite.getComponent(UITransform).width;
this._canvas.height = this.video_sprite.getComponent(UITransform).height;
this._canvas.style.display = 'none';
this._ctx = this._canvas.getContext('2d');
tween(this.ani_dice)
.repeatForever(
tween()
// 0.8秒内放大到1.2倍 + 顺时针旋转180度
.to(0.8, {
scale: new Vec3(1.2, 1.2, 1.2),
angle: 180 // 如果是2D节点用angle3D节点建议用 eulerAngles
}, { easing: 'sineOut' })
// 0.8秒内缩小回1倍 + 再旋转180度总共360度
.to(0.8, {
scale: new Vec3(1, 1, 1),
angle: 360
}, { easing: 'sineIn' })
// 完成一圈后,手动重置 angle 为 0防止数值无限叠加导致精度问题
.call(() => { this.ani_dice.angle = 0; })
)
.start()
}
// 界面关闭时的相关逻辑写在这(已经关闭的界面不会触发onHide)
onHide(result: undefined) {
// app.manager.ui.show<PageMain>({name: 'PageMain', onHide:(result) => { 接收到return的数据并且有类型提示 }})
return result;
}
initUserData(){
this.lab_gold.string = USERDATA.coin.toString()
this.lab_name.string = USERDATA.name
this.remain.string = USERDATA.total_ticket_count.toString()
//this.remain.node.active = USERDATA.total_ticket_count > 0
Tools.ActChild(this.btn_auto, "auto", this._is_continuous)
Tools.ActChild(this.btn_auto, "no_auto", !this._is_continuous)
}
swichBuyAndPlay(){
this.btns_buy.active = USERDATA.total_ticket_count <= 0
this.btn_play.active = USERDATA.total_ticket_count > 0
}
bindVideoEvents() {
if (this.video_bg) {
this.video_bg.node.on('ready-to-play', () => {
this._video = this.video_bg.nativeVideo
}, this);
}
}
/** 初始化方向选择 */
initDir(){
for (let i = 0; i < 2; i++) {
const element = this.n_dir[i];
Tools.ActChild(element, "light", i == this._move_dir)
Tools.ActChild(element, "dark", i != this._move_dir)
}
}
getRwData(){
Tools.httpReq("game/lotteryPool", {}, (res:any) => {
for (let index = 0; index < res.length; index++) {
const d = res[index];
Tools.SetChildText(this.blocks, String(d.grid_number) + "/num", d.ui_text)
}
})
}
/** 购买游戏次数 */
buyTimes(a : any, d : any){
Tools.httpReq("game/buyLotteryTickets", { "count" : d }, (res:any) => {
USERDATA.coin = res.coin
USERDATA.total_ticket_count = res.total_ticket_count
this.swichBuyAndPlay()
this.initUserData()
})
}
chooseDir(d:any, dir: number){
this._move_dir = dir
this.initDir()
}
/** 点击提示 */
onClickTips(){
app.manager.ui.show<PageTips>({name: 'PageTips'})
}
/** 点击音乐 */
oncClickMusic(){
let music = app.manager.sound.isMusicPlaying
music ? app.manager.sound.stopMusic() : app.manager.sound.playDefaultMusic()
this.loadRes(music ? "main_btn_sounds_off" : "main_btn_sounds_on", Asset, (res: ImageAsset)=>{
let sp = new SpriteFrame()
let tex = new Texture2D();
tex.image = res;
sp.texture = tex
this.btn_music.getComponent(Sprite).spriteFrame = sp
});
}
/** 点击历史 */
onClickRwHis(){
app.manager.ui.show<PageRewardhistory>({name: 'PageRewardhistory'})
}
btnGameAuto(){
this._is_continuous = !this._is_continuous
this.initUserData()
}
btnPlayGary(){
this.btn_play.getComponent(Sprite).grayscale = true
Tools.GetChildComp(this.btn_play, "lab", Sprite).grayscale = true
}
btnPlayWhite(){
this.btn_play.getComponent(Sprite).grayscale = false
Tools.GetChildComp(this.btn_play, "lab", Sprite).grayscale = false
}
/** 开始游戏 */
btnGameStart(){
if (this._b_sequence.length != this._sign_position.length) {
return app.manager.ui.showToast(Tools.GetLocalized("数据错误"))
}
if (this._is_playing) return
this._is_playing = true
this.btnPlayGary()
Tools.httpReq("game/playStart", {
"direction" : this._move_dir
}, (res:any) => {
this._game_data = res
this._move_dir = res.direction
USERDATA.total_ticket_count--
this.initUserData()
let steps = 0
for (let i = 0; i < res.roll_array.length; i++) {
const element = res.roll_array[i];
steps += element
}
let index:any = this._b_sequence.findIndex(v => v == steps.toString())
let b = this.blocks.getChildByName(steps.toString())
// 计算移动路径
let path = this.calculatePath(index, steps);
// 播放骰子动画
this.playDiceAni(res.roll_array)
// 判断是否是豹子
let baozi = this._game_data.is_win == 1
Tools.ActChild(this.rw, "bg/bg_title_super", baozi)
Tools.ActChild(this.rw, "bg/bg_title", !baozi)
this.gold_rain.active = baozi
// 如果是豹子,展示豹子特效后停止继续
if (baozi){
this.scheduleOnce(() => {
app.manager.sound.playEffect({name : "effect/eff_unsheathe"})
for (let i = 0; i < this.dices.length; i++) {
let eff = this.dices[i].getChildByPath("dice/eff")
eff.active = true
eff.setPosition(new Vec3(-25, -40, 0))
tween(eff)
.to(0.5, { position: new Vec3(25, 45, 0) })
.call(() => {
this.scheduleOnce(() => {
eff.active = false
}, 0.2)
})
.start()
}
this.scheduleOnce(() => {
Tools.ActChild(this.rw, "bg/win_lab/en", LANG_SET.langIndex == LangType.EN)
Tools.ActChild(this.rw, "bg/win_lab/zh", LANG_SET.langIndex == LangType.ZH)
Tools.SetChildText(this.rw, "bg/win_lab/" + LANG_ENUM[LANG_SET.langIndex] + "/num", this._game_data.win_coin)
this.rw.active = true
app.manager.sound.playEffect({name : "effect/eff_gold_rain"})
app.manager.sound.playEffect({name : "effect/eff_bigwin"})
USERDATA.coin = this._game_data.coin
USERDATA.total_ticket_count = this._game_data.total_ticket_count
this._is_playing = false
this.initUserData()
this.swichBuyAndPlay()
this.btnPlayWhite()
}, 1)
}, 1.2)
}else {
// 骰子总数展示
this.scheduleOnce(() => {
this.dice_all.active = true
Tools.SetChildText(this.dice_all, "dice/lab", steps.toString())
}, 1.2)
this.scheduleOnce(() => {
this.dice_all.active = false
}, 2.4)
//亮灯
this.scheduleOnce(() => {
Tools.ActChild(b, "light", true)
// 设置初始位置
this.sign.setPosition(this._sign_position[index]);
this.sign.active = true;
}, 2.4);
let lastIndex = index
if (this._move_dir == M_DIR.CLOCKWISE) {
lastIndex = (lastIndex + steps) % this._sign_position.length;
} else {
lastIndex = (lastIndex - steps + this._sign_position.length) % this._sign_position.length;
}
// 移动标签
this.scheduleOnce(() => {
// 执行动画
this.animateSign(path, steps * 0.12, lastIndex);
Tools.ActChild(b, "light", false)
}, 3);
}
},()=>{
this._is_playing = false
})
}
/**
* 计算移动路径
* @param index 当前位置索引
* @param steps 移动步数
*/
private calculatePath(index:number, steps: number): Vec3[] {
let path: Vec3[] = [];
let currentIndex = index;
for (let i = 0; i < steps; i++) {
if (this._move_dir == M_DIR.CLOCKWISE) {
currentIndex = (currentIndex + 1) % this._sign_position.length;
} else {
currentIndex = (currentIndex - 1 + this._sign_position.length) % this._sign_position.length;
}
path.push(this._sign_position[currentIndex]);
}
return path;
}
/**
* 执行动画
* @param path 移动路径
* @param duration 总时间(秒)
* @param lastIndex 最后一个位置索引
*/
private animateSign(path: Vec3[], duration: number, lastIndex: number = 0) {
let totalTime = duration;
let segmentTime = totalTime / path.length;
let moveNext = (index: number) => {
let targetPos = path[index];
app.manager.sound.playEffect({name : "effect/eff_jump", volume: 0.5})
/** 跟随闪烁 */
this.scheduleOnce(() => {
let n = this._sign_position.findIndex(v => v == targetPos)
let nowPoint = this._b_sequence[n]
let light = this.blocks.getChildByName(nowPoint).getChildByName("light")
light.active = true
let opc = light.getComponent(UIOpacity)
opc.opacity = 0
tween(opc)
.to(0.12, { opacity: 255 }) // 渐隐
.to(0.12 / 2, { opacity: 0 }) // 渐显 tween(opc)
.call(() => {
light.active = false
opc.opacity = 255
})
.start();
}, 0.06 / 2);
tween(this.sign)
.to(segmentTime, { position: targetPos }, { easing: "linear" })
.call(() => {
if (index + 1 >= path.length) {
this.scheduleOnce(() => {
this.sign.active = false;
let b = this._b_sequence[lastIndex]
if (this._game_data.win_coin >= 0){
app.manager.sound.playEffect({name : "effect/eff_start"})
}else{
app.manager.sound.playEffect({name : "effect/eff_finish", volume: 0.5})
}
this.blinkLightWithTween(this.blocks.getChildByName(b).getChildByName("light"), 1, 2)
}, 0.5);
return;
}
this.scheduleOnce(() => {
moveNext(index + 1);
}, 0.06);
})
.start();
};
moveNext(0);
}
/** 闪烁动画
* @param node 节点
* @param duration 持续时间(秒)
* @param times 闪烁次数
*/
private blinkLightWithTween(node: Node, duration: number, times: number) {
let opc = node.getComponent(UIOpacity);
if (!opc) return;
node.active = true
/** 胜利失败动画 */
this.video_bg.clip = this._game_data.win_coin >= 0 ? this.video_win : this.video_lose
app.manager.sound.playEffect({name : this._game_data.win_coin >= 0 ? "effect/eff_happy" : "effect/eff_thunder", volume: 0.5})
this.scheduleOnce(() => {
this.video_bg.clip = this.video_normal
}, 3.2);
let blinkCount = 0;
const blink = () => {
tween(opc)
.to(duration / 2, { opacity: 0 }) // 渐隐
.to(duration / 2, { opacity: 255 }) // 渐显
.call(() => {
blinkCount++;
if (blinkCount < times) {
blink(); // 重复执行
// 游戏结束
}else{
opc.opacity = 255
node.active = false
this._is_playing = false
if (this._is_continuous){
let lab = Tools.AddChild(this.lab_point, this._game_data.win_coin > 0 ? this.lab_add : this.lab_sub)
let cb = () => {
lab.destroy()
USERDATA.coin = this._game_data.coin
USERDATA.total_ticket_count = this._game_data.total_ticket_count
this.initUserData()
this.swichBuyAndPlay()
if (USERDATA.total_ticket_count > 0){
this.btnGameStart()
}else{
this.btnPlayWhite()
}
}
// T5是再来一次不需要飘字
if (this._game_data.tier != "T5"){
let pos = new Vec3(node.parent.position.x, node.parent.position.y + 80, 0 )
lab.setPosition(pos)
lab.active = true
Tools.SetText(lab, this._game_data.win_coin > 0 ? "+" + this._game_data.win_coin : this._game_data.win_coin)
app.manager.sound.playEffect({name : this._game_data.win_coin > 0 ? "effect/eff_win" : "effect/eff_lose"})
tween(lab)
.by(1, { position: new Vec3(0, 200, 0) })
.call(()=>{
cb()
})
.start()
}else {
cb()
}
}else {
if (this._game_data.tier != "T5"){
if (this._game_data.win_coin > 0){
Tools.ActChild(this.rw, "bg/win_lab/en", LANG_SET.langIndex == LangType.EN)
Tools.ActChild(this.rw, "bg/win_lab/zh", LANG_SET.langIndex == LangType.ZH)
Tools.SetChildText(this.rw, "bg/win_lab/" + LANG_ENUM[LANG_SET.langIndex] + "/num", this._game_data.win_coin)
app.manager.sound.playEffect({name : this._game_data.win_coin > 0 ? "effect/eff_win" : "effect/eff_lose"})
this.rw.active = true
}else{
this.norw.active = true
}
}
USERDATA.coin = this._game_data.coin
USERDATA.total_ticket_count = this._game_data.total_ticket_count
this.swichBuyAndPlay()
this.initUserData()
this.btnPlayWhite()
}
}
})
.start();
};
blink();
}
/** 播放骰子动画 */
playDiceAni(array:any[]){
app.manager.sound.playEffect({name : "effect/shaizi"})
for (let i = 0; i < this.dices.length; i++) {
let dice = this.dices[i]
let animation = dice.getComponent(Animation);
if (animation) {
this.scheduleOnce(() => {
animation.play();
}, math.random() * 0.1);
animation.once(Animation.EventType.FINISHED, () => {
this.loadSprite("dice_" + array[i], dice.getChildByName("dice"))
});
}
}
}
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
});
}
closeRw(){
this.rw.active = false
this.norw.active = false
}
update(){
if (this._video) {
this.updateTexture();
}
}
private async updateTexture() {
if (this._isUpdating) return;
this._isUpdating = true;
try {
this._ctx.save(); // 保存当前状态
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
// 绘制视频帧
this._ctx.drawImage(this._video, 0, 0, this._canvas.width, this._canvas.height);
this._ctx.restore(); // 恢复原始状态
const canvasData = this._canvas;
// 销毁旧纹理
if (this._currentSpriteFrame) {
const oldTexture = this._currentSpriteFrame.texture;
oldTexture?.destroy();
this._currentSpriteFrame.destroy();
}
const texture = new Texture2D();
texture.image = new ImageAsset(canvasData);
const spriteFrame = new SpriteFrame();
spriteFrame.texture = texture;
this.video_sprite.spriteFrame = spriteFrame;
this._currentSpriteFrame = spriteFrame;
} finally {
this._isUpdating = false;
}
}
}