Files
2026-05-23 22:10:14 +08:00

820 lines
22 KiB
JavaScript

/******************************************************************
* 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( "<outline color=black width=2>%s</outline>", 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() {
},
} );