import { _decorator, EventTouch, Node, Quat, math, Camera, sys, Vec3, Label, geometry, ProgressBar, tween, easing, Tween, UIOpacity} from 'cc'; import { Game } from '../game/Game'; import { BaseExp } from '../core/base/BaseExp'; import { autoBind } from '../extend/AutoBind'; import { Constants } from '../data/Constants'; import { uiMgr } from '../core/manager/UIManager'; import { GunAttribute, userIns } from '../data/UserData'; import MsgHints from '../utils/MsgHints'; import List from '../third/List'; import { TaskEnemyItem } from '../items/item/TaskEnemyItem'; import { BulletMagazine } from '../game/BulletMagazine'; import { settingData } from '../data/SettingData'; import i18n from '../core/i18n/runtime-scripts/LanguageData'; import PlatformSystem from '../platform/PlatformSystem'; import { GameEnums } from '../data/GameEnums'; const { ccclass, property } = _decorator; const { clamp, toRadian } = math; @ccclass('GunfightShootUI') export class GunfightShootUI extends BaseExp { @autoBind({ type: Node, tooltip: "轮盘节点" }) public wheel: Node; @autoBind({ type: Node, tooltip: "标准贴图" }) public scopeOverlay: Node; @autoBind({ type: Node, tooltip: "准心" }) public crossHair: Node; @autoBind({ type: ProgressBar, tooltip: "步枪子弹进度条" }) public rifle_bullet_progressBar: ProgressBar; @autoBind({ type: Label, tooltip: "步枪子弹数文本" }) public rifle_bullet_num_label: Label; @autoBind({ type: BulletMagazine, tooltip: "步枪子弹列表" }) public rifle_bullets_scrollView: BulletMagazine; @autoBind({ type: BulletMagazine, tooltip: "狙击枪子弹列表" }) public snipe_bullets_scrollView: BulletMagazine; @autoBind({ type: Node, tooltip: "步枪显示子弹的总的节点" }) public rifle_bullets_bg: Node; @autoBind({ type: List, tooltip: "敌人任务列表" }) public task_scrollView: List; @autoBind({ type: ProgressBar, tooltip: "玩家生命进度条" }) public hpProgressBar: ProgressBar; @property({type: [Node], tooltip: "要隐藏的所有按钮"}) public hiddeNodes: Array = []; @autoBind({ type: ProgressBar, tooltip: "换弹夹时间进度条" }) public reloadProgressBar: ProgressBar; @autoBind({ type: Node, tooltip: "玩家受到伤害呼吸闪烁" }) public injury_blood: Node; @autoBind({ type: Label, tooltip: "玩家枪的名字" }) public gun_name_label: Label; @autoBind({ type: Label, tooltip: "关卡名字" }) public gk_num_lable: Label; @autoBind({ type: Node, tooltip: "切枪按钮" }) public cut_gun_btn: Node; @autoBind({ type: Label, tooltip: "剩余切换次数" }) public cut_num_label: Label; @autoBind({ type: Node, tooltip: "切枪看视频的图标按钮" }) public cut_video: Node; /** 子弹飞行距离 */ public bulletDistance: number = 800; /** 当前相机绕 X 轴的旋转角度 */ private currentXRotation: number = 0; /** 当前相机绕 Y 轴的旋转角度 */ private currentYRotation: number = 0; /** 左右旋转角度的最大限制值 */ private maxHorizontalAngle: number = 180; /** 上下旋转角度的最大限制值 */ private maxVerticalAngle: number = 70; /** 相机的原始视野值 */ private originalFov: number; /** 玩家节点的初始旋转四元数 */ private initialPlayerRotation: Quat = new Quat(); /** 相机节点的初始旋转四元数 */ private initialCameraRotation: Quat = new Quat(); /** 相机放大后的目标视野值 */ private targetFov: number; /** 标记相机是否正在进行放大操作 */ private isZoomingIn: boolean = false; /** 标记相机是否正在进行缩小操作 */ private isZoomingOut: boolean = false; /** 相机视野放大的速度 */ private zoomSpeed: number = 0; /** 期望的缩放时间,单位为秒 */ private zoomDuration: number = 0.2; /** 记录镜头开始放大的时间 */ private zoomStartTime: number = 0; /** 标记是否在2秒内完成放大 */ private zoomValid: boolean = false; /** 标记是否开镜 */ private _isScopeOpen: boolean = false; get isScopeOpen() { return this._isScopeOpen; } set isScopeOpen(value: boolean) { this.gunDataUI(); if (this._isScopeOpen !== value) { this._isScopeOpen = value; if (value) { this.openScope(); } else { this.closeScope(); } } } /**镜头的位置*/ private wheelPos: Vec3 = Vec3.ZERO; //任务数据 private taskDatas: Array = []; //是否正在被击中 private isHurtRun: boolean = false; start() { this.hasAnim = false; this.closeOnBlank = false; this.injury_blood.active = false; this.reloadProgressBar.node.active = false; if(!Constants.isDebug){ this.hiddeNodes.forEach(e => e.active = false); } this.wheelPos = this.wheel.position.clone(); //敌人数量改变 this.register(Constants.eventName.enemy_num_change, this.loadTaskData.bind(this)); //枪的数量发生变化 this.register(Constants.eventName.gun_num_change, this.gunNumChange.bind(this)); //监听当前节点触摸事件 this.node.on(Node.EventType.TOUCH_START, this.onNodeTouchMove, this); this.node.on(Node.EventType.TOUCH_MOVE, this.onNodeTouchMove, this); this.node.on(Node.EventType.TOUCH_END, this.onCustomNodeTouchEnd, this); this.node.on(Node.EventType.TOUCH_CANCEL, this.onCustomNodeTouchEnd, this); //监听轮盘点击事件 监听轮盘的触摸开始事件,当触摸开始时调用 onWheelClick 方法 this.wheel.on(Node.EventType.TOUCH_START, this.onWheelClick, this); this.wheel.on(Node.EventType.TOUCH_END, this.onWheelRelease, this); this.wheel.on(Node.EventType.TOUCH_CANCEL, this.onWheelRelease, this); } public hide(){ uiMgr.getPageComponent(Constants.mainUIs.main)?.playOrStopBGM(false); } /** * 摄像机初始信息 * @param args */ public originalInitial(){ //记录摄像机原始视野 this.originalFov = Game.I.camera.getComponent(Camera).fov; //录初始旋转角度 this.initialPlayerRotation = Game.I.player.node.rotation.clone(); this.initialCameraRotation = Game.I.camera.node.rotation.clone(); this.currentYRotation = this.initialPlayerRotation.x; this.currentXRotation = this.initialPlayerRotation.y; } public show(...args: any[]){ this.loadTaskData(); this.gunNumChange(); } /** * 枪的数量发生变化 是否显示切枪按钮 */ public gunNumChange(){ const isMoreGuns: boolean = userIns.data.guns.length > 1; this.cut_gun_btn.active = isMoreGuns; this.cut_video.active = isMoreGuns; if(userIns.data.cutNum > 0){ this.cut_num_label.string = `x${userIns.data.cutNum}`; this.cut_video.active = false; }else{ this.cut_num_label.string = `lack`; this.cut_video.active = true; } } /** * enemyId total count enemyData * 加载数据 */ public loadTaskData() { const data: any = userIns.getCurLevelData(); this.gk_num_lable.string = i18n("main.关卡任务 %{value}",{value: data.id}); this.taskDatas = Game.I.buildEnemys.enemyTypeRecords; this.task_scrollView.numItems = this.taskDatas.length; //加载子弹的数据 this.gunDataUI(); } /** * 任务数据 * @param item item节点 * @param idx 数据下标 */ public setTaskItemData(item: Node, idx: number) { item.getComponent(TaskEnemyItem).init(this.taskDatas[idx]); } /** * 设置枪的数据 */ public gunDataUI() { const gData:any = Game.I.player.pData; if(Game.I.isGameOver || Game.I.isPause || !Game.I.player.gun || !gData){ return; } this.crossHair.active = true;//准心 //步枪没有开镜贴图 const isRifle: boolean = this.weaponCategoryGun() == GameEnums.weaponCategory.rifle; this.scopeOverlay.active = !isRifle; this.gun_name_label.string = gData.name_lang; const isSnipeGun: boolean = gData.type == 1; //换弹夹进度条 const isMagazine: boolean = Game.I.player.isReloadMagazine; if(isMagazine){ this.rifle_bullets_bg.active = false; this.snipe_bullets_scrollView.node.active = false; }else{ //步枪子弹视图 this.rifle_bullets_bg.active = !isSnipeGun; //狙击子弹视图列表 this.snipe_bullets_scrollView.node.active = isSnipeGun; if(isSnipeGun){ this.snipe_bullets_scrollView.magazineNum = gData.magazine; }else{ this.rifle_bullets_scrollView.magazineNum = gData.magazine; //子弹进度条 let s_bullet: number = gData.magazine - Game.I.player.gun.shotBullets; this.rifle_bullet_progressBar.progress = s_bullet / gData.magazine; this.rifle_bullet_num_label.string = `${s_bullet}/${gData.magazine}`; } } } /** * 更换弹夹动画 * @param time 换弹时间 单位秒 * @param complete 完成回调 */ public reloadMagazineing(time: number,complete?: Function) { if(time <= 0){ complete?.(); }else{ this.reloadProgressBar.node.active = true; this.reloadProgressBar.progress = 1; this.rifle_bullets_bg.active = false; this.snipe_bullets_scrollView.node.active = false; //创建定时器动画 tween(this.reloadProgressBar) .to(time, { progress: 0 }, { easing: easing.linear, onStart: () => { tween(this.reloadProgressBar).stop(); }, onComplete: () => { this.reloadProgressBar.node.active = false; complete?.(); } }) .start(); } } /** * 根据切换枪的stability设置镜头的稳定性 */ public crossHairStability() { const gData:any = Game.I.player.pData; if(!gData)return; this.gunDataUI(); this.wheel.position = this.wheelPos; //获取当前枪的最大稳定性值 const maxStability:number = userIns.getGunMaxValue(gData, GunAttribute.stability).totalValue; // 计算稳定性因子 const stabilityFactor = Math.max(0, 1 - (gData.stability / maxStability)); //根据稳定性因子动态调整振幅 const amplitude = 5 + (20 * stabilityFactor); //基础抖动持续时间0.3-0.6秒,稳定性越高持续时间越短 const duration = 0.6 - (0.3 * (1 - stabilityFactor)); let elapsed = 0; const updateShake = (deltaTime: number) => { elapsed += deltaTime; //双轴随机偏移 const offsetX = amplitude * (Math.random() - 0.5) * Math.max(1 - elapsed/duration, 0); const offsetY = amplitude * (Math.random() - 0.5) * Math.max(1 - elapsed/duration, 0); this.wheel.position = new Vec3( this.wheelPos.x + offsetX, this.wheelPos.y + offsetY, this.wheelPos.z ); //自动结束 if(elapsed >= duration) { this.wheel.position = this.wheelPos; this.unschedule(updateShake); } }; this.unschedule(updateShake); // 使用游戏时间而非系统时间 this.schedule(updateShake, 0.02); } /** * 屏幕触摸事件 * 触摸移动时,根据触摸的位置计算旋转角度,并应用到节点上 * @param event */ private onNodeTouchMove(event: EventTouch) { const delta = event.getDelta(); let f: number = this._isScopeOpen ? 0.2 : 0.6; //计算旋转角度 相机旋转的灵敏度 值越大移动越快 灵敏度越高 this.currentYRotation -= delta.x * settingData.data.sensitivity * f; this.currentXRotation -= delta.y * settingData.data.sensitivity * f; //分别限制左右和上下旋转角度 this.currentYRotation = clamp(this.currentYRotation, -this.maxHorizontalAngle, this.maxHorizontalAngle); this.currentXRotation = clamp(this.currentXRotation, -this.maxVerticalAngle, this.maxVerticalAngle); //计算水平和垂直旋转的四元数 const yQuat = new Quat(); Quat.fromEuler(yQuat, 0, this.currentYRotation, 0); const xQuat = new Quat(); Quat.fromEuler(xQuat, +this.currentXRotation, 0, 0); //合并旋转 let finalQuat = new Quat(); Quat.multiply(finalQuat, yQuat, xQuat); //将最终旋转与初始摄像机旋转合并 Quat.multiply(finalQuat, this.initialPlayerRotation, finalQuat); //应用旋转到摄像机 Game.I.player.node.rotation = finalQuat; } /** * 触摸结束 * 触摸结束时 触摸结束处理方法 */ private onCustomNodeTouchEnd() { //触摸结束,可添加其他逻辑 } /** * 开始慢慢放大视野 */ private onWheelClick() { this.isScopeOpen = true; } /** * 恢复原始视野 */ private onWheelRelease() { if(Game.I.player.isReloadMagazine)return; if(!this.zoomValid){ this.isScopeOpen = false; return } const type:number = this.weaponCategoryGun(); switch(type){ case GameEnums.weaponCategory.sniper://狙击枪 Game.I.player.shoot(); this.scheduleOnce(()=>{ this.isScopeOpen = false; },0.5) break case GameEnums.weaponCategory.dmr://连狙 Game.I.player.shoot(); break case GameEnums.weaponCategory.rifle://步枪 this.isScopeOpen = false; break } } /** * 开始开镜 */ private openScope() { const gData = Game.I.player.pData; if(!gData)return; if(Game.I.player.isReloadMagazine)return; const isRifle: boolean = this.weaponCategoryGun() == GameEnums.weaponCategory.rifle; const type:number = this.weaponCategoryGun(); switch(type){ case GameEnums.weaponCategory.sniper://狙击枪 //这儿已经在onWheelRelease处理过了 break case GameEnums.weaponCategory.dmr://连狙 //这儿已经在onWheelRelease处理过了 break case GameEnums.weaponCategory.rifle://步枪 是步枪直接连续开火 Game.I.player.shoot(); break } this.isZoomingIn = true; //将zoomingSpeed 转换为持续时间(450对应1.2秒) this.zoomDuration = 72 / gData.zoomingSpeed; //使用枪械类型决定视口倍数 步枪使用 rifleZoom 狙击枪使用 scopeZoom const zoomMultiplier = isRifle ? 1 / gData.rifleZoom : 1 / gData.scopeZoom; this.targetFov = this.originalFov * zoomMultiplier; //保持原有速度计算逻辑 const fovDifference = this.originalFov - this.targetFov; this.zoomSpeed = fovDifference / this.zoomDuration; this.zoomStartTime = sys.now(); } /** * 关闭开镜 */ private closeScope() { this.isZoomingIn = false; this.isZoomingOut = true; const fovDifference = this.originalFov - this.targetFov; //使用相同 zoomDuration 保证缩镜速度一致 this.zoomSpeed = fovDifference / this.zoomDuration; } /** * 判断是否属于某一种枪 */ public weaponCategoryGun():number{ const gData:any = Game.I.player.pData; if(!gData)return GameEnums.weaponCategory.sniper; if(gData.id == GameEnums.palyerWeaponType.akm ||gData.id == GameEnums.palyerWeaponType.m416){ return GameEnums.weaponCategory.rifle; }else if(gData.id == GameEnums.palyerWeaponType.vss ||gData.id == GameEnums.palyerWeaponType.sks){ return GameEnums.weaponCategory.dmr; }else { return GameEnums.weaponCategory.sniper; } } /** * 计算从枪口出发,沿准心射线方向的点 * @param isBulletEndPos 是否获得子弹的终点 */ public getCrossHairPos(){ //获取屏幕准心位置(世界坐标) const screenPos = this.getScreenCenterPos(); //校准补偿准心的偏移量 //screenPos.x -= 0; //screenPos.y += 0; //从摄像机发射通过准心的射线 const ray = new geometry.Ray(); Game.I.camera.screenPointToRay(screenPos.x, screenPos.y, ray); //获取枪口位置 const muzzlePos = Game.I.player.gun.muzzleNode.worldPosition; return new Vec3( muzzlePos.x + ray.d.x * this.bulletDistance, muzzlePos.y + ray.d.y * this.bulletDistance, muzzlePos.z + ray.d.z * this.bulletDistance); } /** * 返回屏幕中心的世界坐标 */ public getScreenCenterPos(){ const worldPos = this.crossHair.worldPosition.clone(); const camera2D: Camera = Game.I.canvas.getChildByName('Camera2D').getComponent(Camera); let screenPos = camera2D.worldToScreen(worldPos); return screenPos; } /** * 玩家血量低于30%一直闪烁、打一枪的时候闪一下 * @param progress 血量进度 * @returns */ public playerHurtTwinkle(progress: number){ this.hpProgressBar.progress = progress; const isLowHP = progress <= 0.3; const op = this.injury_blood.getComponent(UIOpacity); //如果当前是高血量且正在闪烁 停止并隐藏 if (!isLowHP && this.isHurtRun) { this.isHurtRun = false; Tween.stopAllByTarget(op); this.injury_blood.active = false; return; } if (isLowHP && !this.isHurtRun) {//低血量且未在闪烁时,启动循环闪烁 this.isHurtRun = true; this.injury_blood.active = true; op.opacity = 255; tween(op) .to(0.5, { opacity: 0 }, { easing: easing.linear }) .to(0.5, { opacity: 255 }, { easing: easing.linear }) .repeatForever() .start(); }else if (!isLowHP) {//高血量时触发单次闪烁 this.injury_blood.active = true; Tween.stopAllByTarget(op); op.opacity = 255; tween(op) .to(0.4, { opacity: 0 }, { easing: easing.linear }) .call(() => { this.injury_blood.active = false; }) .start(); } } /** * 点击事件 * @param event 事件 * @param customEventData 数据 */ public onBtnClicked(event: EventTouch, customEventData: any) { super.onBtnClicked(event,customEventData); let btnName = event.target.name; if (btnName === 'pause_btn') { // 暂停页面 uiMgr.show(Constants.popUIs.pauseUI); }else if (btnName === 'shot_btn') {//射击 let type:number = this.weaponCategoryGun(); switch(type){ case GameEnums.weaponCategory.sniper://狙击枪 Game.I.player.shoot(); break case GameEnums.weaponCategory.rifle://步枪 this._isScopeOpen = true; Game.I.player.shoot(); this.scheduleOnce(()=>{ this._isScopeOpen = false; },1.5) break case GameEnums.weaponCategory.dmr://连狙 Game.I.player.shoot(); break } }else if (btnName === 'cut_gun_btn') {//切枪 if(userIns.data.guns.length <= 1){ MsgHints.show('Go unlock more guns'); return; } if(Game.I.player.isReloadMagazine){ MsgHints.show('Loading ammo...'); return; } if(userIns.data.cutNum > 0){ Game.I.player.randomCutGun(); }else{//看视频加切换次数 PlatformSystem.platform.showRewardVideo((f) => { if(f) {//播放视频成功 userIns.data.cutNum += 5; this.cut_num_label.string = `x${userIns.data.cutNum}`; this.cut_video.active = false; } }); } }else if(btnName === 'clean_btn'){//重新加载数据 userIns.removeData(); Game.I.player.pData = userIns.getCurUseGun(); MsgHints.show('重新加载数据完成'); }else if(btnName === 'unlock_btn'){//解锁全部枪数据 userIns.unlockAllGuns(); MsgHints.show('已经解锁全部枪数据'); } } /** * //基准速度值 基准时间3秒 const baseSpeed = 450; const baseTime = 3; //时间 = (基准速度 / 当前速度) * 基准时间 let time: number = (baseSpeed / data.reloadingSpeed) * baseTime; */ /** * 每帧更新,处理视野渐变和检查 2 秒时间限制 * @param deltaTime - 上一帧到当前帧的时间间隔 */ update(deltaTime: number) { if(this.isZoomingIn) { const currentFov = Game.I.camera.fov; if (currentFov > this.targetFov) { const newFov = currentFov - this.zoomSpeed * deltaTime; Game.I.camera.fov = Math.max(newFov, this.targetFov); }else {//检查是否在2秒内完成放大 const elapsedTime = (sys.now() - this.zoomStartTime) / 1000; if (elapsedTime <= 1.5) { this.zoomValid = true; } this.isZoomingIn = false; } }else if(this.isZoomingOut) { const currentFov = Game.I.camera.fov; if (currentFov < this.originalFov) { const newFov = currentFov + this.zoomSpeed * deltaTime; Game.I.camera.fov = Math.min(newFov, this.originalFov); } else { this.isZoomingOut = false; this.zoomValid = false; } } } }