/****************************************************************** * Copyright(C) 2019 - 2020 Nx Studio * * 剧情对话场景 * * ******************************************************************/ const BridgeWindow = require( "bridge.window" ); const NxSpine = require( "nx.fx.spine" ); const BattleController = require( "battle_controller" ); const DARK = cc.color( 57, 57, 57, 255 ); cc.Class( { extends: BridgeWindow, properties: { nodTouch: { default: null, type: cc.Node }, nodBG: { default: null, type: cc.Node }, spAnim: { default: null, type: NxSpine }, nodRoles: { default: null, type: cc.Node }, spAnimMiddle: { default: null, type: NxSpine }, nodDialog: { default: null, type: cc.Node }, nodTell: { default: null, type: cc.Node }, nodSkip: { default: null, type: cc.Button }, nodTemps: { default: null, type: cc.Node }, }, // 重载:参数打开 onOpenConfigs: function( _params ) { // 当前无剧本 this.scenario = nx.bridge.plot.vget( "info" ); if( nx.dt.objEmpty( this.scenario ) ) { nx.error( "$PlotDialogue:对白失败,当前无剧本!" ); this.delayClose(); return; } // 初始化环境 this.doPrepare(); // 视图监听 nx.bridge.plot.vbind( this, [ [ "step", this.onStepChanged.bind( this ) ], [ "key", this.onKeyChanged.bind( this ) ], ] ); // 自动开始 let step = nx.bridge.plot.vget( "step" ); if( step < 0 ) { nx.bridge.plot.next(); } nx.dg = this; }, // 关闭 onDisable: function() { this.stopSFX(); this.stopVoice(); // 视图解绑 nx.bridge.plot.vunbind( this ); }, // 初始化环境 doPrepare: function() { // UI初始化 this.nodDialog.active = false; this.nodTell.active = false; // 可否跳过 this.nodSkip.node.active = false; if( this.scenario && this.scenario.skip ) { this.nodSkip.interactable = false; nx.tween.delayFadeIn( this.nodSkip, "", 3, 1, () => { this.nodSkip.interactable = true; } ); } // 加载角色模型 this.loadRoles(); }, // 执行单步 doStep: function( _data ) { // 无效 if( nx.dt.objEmpty( _data ) ) { return; } // 当前步信息 this.step = _data; // 背景 if( nx.dt.objNEmpty( _data.bg ) ) { this.stepBG( _data.bg ); return; } // 背景音乐 if( nx.dt.objNEmpty( _data.bgm ) ) { this.stepBGM( _data.bgm ); return; } // Spine if( nx.dt.objNEmpty( _data.spine ) ) { this.stepSpine( _data.spine ); return; } // Spine中间层 if( nx.dt.objNEmpty( _data.spine_mid ) ) { this.stepSpineMiddle( _data.spine_mid ); return; } // 讲述 if( nx.dt.objNEmpty( _data.tell ) ) { this.stepTell( _data.tell ); return; } // 对白 if( nx.dt.arrNEmpty( _data.talks ) ) { this.stepTalks( _data.talks ); return; } // 角色出现 if( nx.dt.objNEmpty( _data.appear ) ) { this.stepAppear( _data.appear ); return; } // 角色离开 if( nx.dt.objNEmpty( _data.disappear ) ) { this.stepDisAppear( _data.disappear ); return; } // 角色聚焦 if( nx.dt.objNEmpty( _data.focus ) ) { this.stepFocus( _data.focus ); return; } // 窗体操作 if( nx.dt.objNEmpty( _data.window ) ) { this.stepWindow( _data.window ); return; } // 等待事件 if( nx.dt.objNEmpty( _data.event ) ) { this.stepEvent( _data.event ); return; } // 特殊操作 if( nx.dt.objNEmpty( _data.special ) ) { this.stepSpecail( _data.special ); return; } // 无效处理 nx.error( "$PlotDialogue:无效步骤! ", nx.dt.enjson( _data ) ); nx.bridge.plot.next(); }, // 步数改变 onStepChanged: function( _step, _, _init ) { let step = nx.bridge.plot.stepData(); if( nx.dt.objEmpty( step ) ) { return; } // 执行 this.doStep( step ); }, // ======================================================================== // 角色相关 // ======================================================================== // 加载角色模型 loadRoles: function() { this.nodRoles.removeAllChildren( true ); let roles = nx.dt.objClone( this.scenario.roles ); let temp = nx.gui.find( this.nodTemps, "roleT" ); this.scenario.roles.forEach( ( role ) => { let node = cc.instantiate( temp ); node.opacity = 0; node.parent = this.nodRoles; node.scale = role[ 2 ] || 1.0; node.offset = cc.v2( role[ 3 ] || 0, role[ 4 ] || 0 ); node.y = role[ 4 ] || 0; } ); }, // ======================================================================== // 单步处理:角色 // ======================================================================== // 单步执行:角色出现 stepAppear: function( _data ) { let node = this.nodRoles.children[ _data.who ]; if( !node ) { nx.error( "$PlotDialogue:角色未找到! ", _data.who ); nx.bridge.plot.next(); return; } // 出现 let self = this; let appear = function() { // 出现方式 node.opacity = 255; if( _data.way == "right" ) { node.x = self.node.width / 2; } else if( _data.way == "left" ) { node.x = -self.node.width / 2; } // 自动布局 let nodes = []; self.nodRoles.children.forEach( _n => { if( _n && _n.opacity > 0 ) { nodes.push( _n ); } } ); let space = self.scenario.spaces[ nodes.length - 1 ]; for( let i = 0; i < nodes.length; ++i ) { let rd = nodes[ i ]; let pos = cc.v2( space[ i ], rd.y ); nx.tween.moveTo( rd, "", _data.secs, pos ); } // 延迟下一步 self.scheduleOnce( () => { nx.bridge.plot.next(); }, _data.secs * 0.9 ); }; // 模型载入 if( !node.spine ) { let role = this.scenario.roles[ _data.who ]; let path = cc.path.join( "resDB/models", role[ 1 ], "show" ); node.spine = nx.gui.getComponent( node, "", "nx.fx.spine" ); node.spine.play( path, _data.action, ( _event ) => { if( _event == "start" ) { appear(); } }, true ); nx.gui.setColor( node.spine, "", DARK ); return; } appear(); }, // 单步执行:角色离开 stepDisAppear: function( _data ) { let node = this.nodRoles.children[ _data.who ]; if( !node ) { nx.error( "$PlotDialogue:角色未找到! ", _data.who ); nx.bridge.plot.next(); return; } // 自动布局 let self = this; let autoSpace = function() { let nodes = []; self.nodRoles.children.forEach( _n => { if( _n && _n.opacity > 0 ) { nodes.push( _n ); } } ); let space = self.scenario.spaces[ nodes.length - 1 ]; for( let i = 0; i < nodes.length; ++i ) { let rd = nodes[ i ]; let pos = cc.v2( space[ i ], rd.y ); nx.tween.moveTo( rd, "", _data.secs / 2, pos ); } // 延迟下一步 self.scheduleOnce( () => { nx.bridge.plot.next(); }, _data.secs * 0.45 ); }; nx.tween.fadeOut( node, "", _data.secs / 2, () => { autoSpace(); } ); }, // 单步执行:角色聚焦 stepFocus: function( _data ) { // 音效 this.playSFX( _data.sfx, !!_data.loop ); // 重复聚焦 if( this.focus == _data.who ) { nx.bridge.plot.next(); return; } // 如果聚焦动作为空,则只改变对白对象 if( nx.dt.strEmpty( _data.action ) ) { this.focus = _data.who; let role = this.scenario.roles[ this.focus ]; nx.gui.setString( this.nodDialog, "title/name", nx.text.getKey( role[ 0 ] ) ); nx.gui.setString( this.nodDialog, "content", "" ); this.scheduleOnce( () => { nx.bridge.plot.next(); }, 0.1 ); return; } // 老的还原 if( nx.dt.numGood( this.focus ) ) { let node = this.nodRoles.children[ this.focus ]; let spnode = nx.gui.find( node, "sp" ); cc.Tween.stopAllByTarget( spnode ); cc.tween( spnode ) .to( _data.secs || 0.1, { scale: 1, color: DARK } ) .start(); } // 新的聚焦 this.focus = _data.who; if( !nx.dt.numGood( this.focus ) ) { return; } let node = this.nodRoles.children[ this.focus ]; let spnode = nx.gui.find( node, "sp" ); cc.Tween.stopAllByTarget( spnode ); cc.tween( spnode ) .to( _data.secs || 0.1, { scale: 1.05, color: cc.Color.WHITE } ) .call( () => { nx.bridge.plot.next(); } ) .start(); if( nx.dt.strNEmpty( _data.action ) && node.spine ) { node.spine.action( _data.action, true ); } // 说话者信息 let role = this.scenario.roles[ this.focus ]; nx.gui.setString( this.nodDialog, "title/name", nx.text.getKey( role[ 0 ] ) ); nx.gui.setString( this.nodDialog, "content", "" ); }, // ======================================================================== // 单步处理:动画 // ======================================================================== // 单步执行 stepSpine: function( _data ) { let path = cc.path.join( "prefab/plot/spines", _data.file ); let done = function() { nx.bridge.plot.next(); }; // 播放 nx.gui.setOpacity( this.spAnim, "", 255 ); this.spAnim.play( path, _data.action, ( _key ) => { if( _key == "start" ) { this.playSFX( _data.sfx, !!_data.loop ); return; } if( _key == "complete" && _data.wait ) { done(); return; } }, _data.loop ); if( !_data.wait ) { done(); } }, // 单步执行 stepSpineMiddle: function( _data ) { let self = this; let path = cc.path.join( "prefab/plot/spines", _data.file ); let done = function() { self.spAnimMiddle.stop(); nx.bridge.plot.next(); }; // 播放 nx.gui.setOpacity( this.spAnimMiddle, "", 255 ); this.spAnimMiddle.play( path, _data.action, ( _key ) => { if( _key == "start" ) { this.playSFX( _data.sfx, !!_data.loop ); return; } if( _key == "complete" && _data.wait ) { done(); return; } }, _data.loop ); if( !_data.wait ) { done(); } }, // ======================================================================== // 单步处理:背景 // ======================================================================== // 单步执行 // { "bg": { "file":"bg002", "op":"fadeSpine", "opsecs": 5.3 } }, stepBG: function( _data ) { let path = cc.path.join( "prefab/plot/images", _data.file ); // 延迟更变背景,但是不延迟 if( _data.op == "delay" ) { this.scheduleOnce( () => { nx.gui.setSpriteFrame( this.nodBG, "", path ); }, _data.opsecs || 0.3 ); nx.bridge.plot.next(); return; } nx.gui.setSpriteFrame( this.nodBG, "", path ); // 背景&&动画 淡进淡出切换 if( _data.op == "fadeSpine" ) { nx.tween.fadeOut( this.spAnim, "", _data.opsecs || 0.5 ); nx.tween.fadeIn( this.nodBG, "", _data.opsecs || 0.5, () => { nx.bridge.plot.next(); } ); return; } // 直接下一步 nx.bridge.plot.next(); }, // ======================================================================== // 单步处理:背景音乐 // ======================================================================== // 单步执行 // { "bg": { "file":"bg002" } }, stepBGM: function( _data ) { let cmp = nx.gui.getComponent( this, "", "nx.fx.BGM" ); if( cmp ) { cmp.reset(); cmp.bgmRK = cc.path.join( "prefab/plot/audios", _data.file ); cmp.reActive(); } // 直接下一步 nx.bridge.plot.next(); }, // ======================================================================== // 单步处理:对白 // ======================================================================== // 单步执行 stepTalks: function( _data ) { this.talks = nx.dt.objClone( _data ); this.doTalk(); }, // 单步对话 doTalk: function() { if( nx.dt.arrEmpty( this.talks ) ) { this.nodDialog.active = false; nx.bridge.plot.next(); return; } let data = this.talks.shift(); if( nx.dt.objEmpty( data ) ) { this.doTalk(); return; } this.nodDialog.active = true; // 当前动作 let node = this.nodRoles.children[ this.focus ]; if( node && node.spine ) { // 动作 if( nx.dt.strNEmpty( data.action ) ) { node.spine.action( data.action, true ); } // 播放速度 if( nx.dt.numPositive( data.speed, false ) ) { node.spine.setTimeScale( data.speed ); } } // 内容 if( data.words && nx.dt.strNEmpty( data.words ) ) { let txt = nx.text.getKey( data.words ); txt = nx.text.formatS( "%s", txt ); let cmp = nx.gui.setString( this.nodDialog, "content", txt ); if( cmp ) { cmp.fontSize = data.fontSize || 22; cmp.lineHeight = cmp.fontSize * 1.5; let scale = nx.gui.getComponent( cmp, "", "nx.fx.local.text" ); if( scale ) { scale.autoScale(); } } } // 抖动 if( nx.dt.objNEmpty( data.shake ) ) { let secs = data.shake.secs || 0.5; let offset = cc.v2( data.shake.x || 10, data.shake.y || 10 ); nx.tween.shake( this.nodDialog, "content", secs, offset ); } // 语音 this.playVoice( data.voice ); // 继续 nx.gui.setActive( this.nodDialog, "continue", this.talks.length > 1 ); }, // ======================================================================== // 单步处理:窗体 // ======================================================================== // 单步执行 stepWindow: function( _data ) { // 关闭窗体 if( nx.dt.strNEmpty( _data.close ) ) { nx.bridge.closePanel( _data.close ); } // 打开窗体 if( nx.dt.strNEmpty( _data.open ) ) { nx.bridge.createPanel( _data.open, _data.args || {} ); } nx.bridge.plot.next(); }, // ======================================================================== // 单步处理:事件 // ======================================================================== // 单步执行 stepEvent: function( _data ) { this.wkey = _data.key || ""; }, // 触发事件改变 onKeyChanged: function( _key, _, _init ) { if( _init || nx.dt.strEmpty( _key ) ) { return; } if( this.wkey == _key ) { nx.bridge.plot.next(); } }, // ======================================================================== // 单步处理:特殊操作 // ======================================================================== // 单步执行 stepSpecail: function( _data ) { if( nx.dt.strEmpty( _data.key ) ) { nx.error( "$PlotDialogue:特殊操作失败,关键字缺失!" ); nx.bridge.plot.next(); return; } // 全隐 if( _data.key == "hide" ) { nx.bridge.plot.next(); this.scheduleOnce( () => { this.node.opacity = 0; this.nodTouch.active = false; nx.gui.getComponent( this, "", cc.BlockInputEvents ).enabled = false; }, _data.delay || 0.1 ); return; } // 全显 if( _data.key == "show" ) { this.node.opacity = 255; this.nodTouch.active = true; nx.gui.getComponent( this, "", cc.BlockInputEvents ).enabled = true; nx.bridge.plot.next(); return; } // 首战 if( _data.key == "fstBattle" ) { let BC = BattleController.getInstance(); BC.requestUseHeroBattle(); this.wkey = _data.event; return; } }, // ======================================================================== // 单步处理:讲述 // ======================================================================== // 单步讲述 stepTell: function( _data ) { if( nx.dt.objEmpty( _data ) ) { this.nodTell.active = false; nx.bridge.plot.next(); return; } this.nodTell.active = true; let txt = nx.text.getKey( _data.words ); nx.gui.setString( this.nodTell, "txt", txt ); let self = this; nx.tween.fadeIn( this.nodTell, "txt", _data.fadeIn, () => { self.scheduleOnce( () => { nx.tween.fadeOut( self.nodTell, "txt", _data.fadeOut, () => { self.nodTell.active = false; nx.bridge.plot.next(); } ); }, _data.delay ); } ); this.playVoice( _data.voice ); }, // ======================================================================== // 音效处理 // ======================================================================== // 互斥播放 playSFX: function( _sfx, _loop = true ) { if( nx.dt.strEmpty( _sfx ) ) { return; } let path = cc.path.join( "prefab/plot/audios", _sfx ); // 老音效判断 if( this.sfxAction ) { // 是否已经结束 if( !nx.audio.isSFXPlaying( this.sfxAction.handle ) ) { this.sfxAction.path = ""; this.sfxAction.handle = -1; } // 重复播放 if( this.sfxAction.path == path ) { return; } // 关闭 if( this.sfxAction.handle > 0 ) { nx.audio.stopSFX( this.sfxAction.handle ); } this.sfxAction = null; } nx.audio.playSFX( path, _loop, ( _err, _id, _path ) => { if( !_err ) { this.sfxAction = { path: _path || path, handle: _id, } } } ); }, // 立即清理 stopSFX: function() { if( this.sfxAction ) { nx.audio.stopSFX( this.sfxAction.handle ); this.sfxAction = null; } }, // ======================================================================== // 语音处理 // ======================================================================== // 播放 playVoice: function( _voice ) { if( nx.dt.strEmpty( _voice ) ) { return; } let path = cc.path.join( "prefab/plot/audios", _voice ); // 老音效判断 if( this.sfxVoice ) { // 是否已经结束 if( !nx.audio.isSFXPlaying( this.sfxVoice.handle ) ) { this.sfxVoice.path = ""; this.sfxVoice.handle = -1; } // 重复播放 if( this.sfxVoice.path == path ) { return; } // 关闭 if( this.sfxVoice.handle > 0 ) { nx.audio.stopVoice( this.sfxVoice.handle ); } this.sfxVoice = null; } nx.audio.playVoice( path, ( _err, _id, _path ) => { if( !_err ) { this.sfxVoice = { path: _path || path, handle: _id, } } } ); }, // 语音关闭 stopVoice: function() { if( this.sfxVoice ) { nx.audio.stopSFX( this.sfxVoice.handle ); this.sfxVoice = null; } }, // ======================================================================== // 用户输入 // ======================================================================== // 点击跳过 onTouchSkip: function() { nx.bridge.plot.done(); this.close(); }, // 点击继续 onTouchContinue: function() { }, } );