Project1

标题: RPG Maker MV 的源码研究 其六 [打印本页]

作者: 沉滞的剑    时间: 2019-8-15 00:54
标题: RPG Maker MV 的源码研究 其六
这里是还活着的楼主, 不多说, 继续
对了想起来一个JS细节
关于function.bind(this)的解释
JavaScript的特性是静态作用域, 所有变量的作用域都在声明的时候就被确定下来了
唯独有一个例外就是this, 它并不是在声明而是在调用的时被确定指向
而使用bind可以强行绑定this的到当前的对象上来
否则再别的场景调用的时候, this可不一定指向哪里的

=================
第五章 Scene_Title: 自带BGM的男人

我们之前看完了Scene_Boot
得知了Scene是具有生命周期钩子的, 比如create和run, 并被SceneManager调度
虽然Scene_Boot是伟大的, 但它的生命却是短暂的
而Scene_Title是第一个真正拥有update能力的Scene
那我们就先了解下Scene_Title的生命周期吧

构造函数什么都没干, 略过
先看看第一个create函数
  1. Scene_Title.prototype.create = function() {
  2.     Scene_Base.prototype.create.call(this);
  3.     this.createBackground();
  4.     this.createForeground();
  5.     this.createWindowLayer();
  6.     this.createCommandWindow();
  7. }
复制代码
嗯, 看得出它调用了更多的create...
我们以createBackground为例来看看每个create具体都做了什么
  1. Scene_Title.prototype.createBackground = function() {
  2.     this._backSprite1 = new Sprite(ImageManager.loadTitle1($dataSystem.title1Name));
  3.     this._backSprite2 = new Sprite(ImageManager.loadTitle2($dataSystem.title2Name));
  4.     this.addChild(this._backSprite1);
  5.     this.addChild(this._backSprite2);
  6. };
复制代码

$dataSystem就是之前说的由DataManager加载进来的System.json游戏数据
对应工具数据库中的系统选项卡
然后, 嗯, ImageManager又出现了, 估计下一篇就要先讲它了, 不然欠得债就越来越多啦~

我们目前只要知道每个图片的底层加载的是一个 new Image() 对象就可以了
Image本质上就是个<image>标签, 通过设置src路径而让浏览器加载图片

图片加载是个极其复杂的过程, 一个不完整的调用栈如下:
ImageManager.loadTitle1 => 加载标题背景图
ImageManager.loadBitmap => 加载位图共通方法
ImageManager.loadNormalBitmap => 加载非空位图共通方法
Bitmap.load => 创建Bitmap对象静态方法
Bitmap.prototype._requestImage => 创建Image对象, 异步请求资源
Bitmap.prototype._onLoad => 资源加载完毕回调函数
....

这回我们还是主要关心场景的流程部分
addChild是之前继承的PIXI.Container的方法
Scene_Title是背景图, 前景图的父容器,也同样是WindowLayer的父容器,
而CommandWindow是WindowLayer的子节点,
其中关键的addWindow方法和createWindowLayer是由Scene_Base继承而来的
  1. Scene_Title.prototype.createCommandWindow = function() {
  2.     this._commandWindow = new Window_TitleCommand();
  3.     this._commandWindow.setHandler('newGame',  this.commandNewGame.bind(this));
  4.     this._commandWindow.setHandler('continue', this.commandContinue.bind(this));
  5.     this._commandWindow.setHandler('options',  this.commandOptions.bind(this));
  6.     this.addWindow(this._commandWindow);
  7. };
复制代码
WindowLayer和Window也是很重要的游戏对象不过...你懂的, 以后再说
现在只要理解的就是选项窗口的每一项都绑定了一个Scene_Title的回调函数
也就是每当玩家做出选择行为的时候, 对应的回到函数就会做出相应
比如你选择新游戏, 那么commandNewGame就会被触发

create看完了看start
  1.     Scene_Base.prototype.start.call(this);
  2.     SceneManager.clearStack();
  3.     this.centerSprite(this._backSprite1);
  4.     this.centerSprite(this._backSprite2);
  5.     this.playTitleMusic();
  6.     this.startFadeIn(this.fadeSpeed(), false);
  7. };
复制代码


嗯, 在SceneManager调用了Scene_Title的start方法,
回过头来Scene_Title又调用了SceneManager的clearStack方法
嗯有来有回, 这是个很容易的不需要留到以后去看的方法
  1. SceneManager.clearStack = function () {
  2.     this._stack = [];
  3. };
复制代码
这个方法将SceneManager的场景栈典型的应用时用到压栈的方式从父场景进入子场景
父场景暂停, 压入子场景,等待子场景结束, 子场景弹出就恢复了父场景centerSprite只是单纯得让精灵居中的方法

  1. Scene_Title.prototype.centerSprite = function(sprite) {
  2.     sprite.x = Graphics.width / 2;
  3.     sprite.y = Graphics.height / 2;
  4.     sprite.anchor.x = 0.5;
  5.     sprite.anchor.y = 0.5;
  6. };
复制代码
后两个方法是播放背景音乐和开头的淡入效果
  1. Scene_Title.prototype.playTitleMusic = function() {
  2.     AudioManager.playBgm($dataSystem.titleBgm);
  3.     AudioManager.stopBgs();
  4.     AudioManager.stopMe();
  5. };
复制代码
  1. Scene_Base.prototype.startFadeIn = function(duration, white) {
  2.     this.createFadeSprite(white);
  3.     this._fadeSign = 1;
  4.     this._fadeDuration = duration || 30;
  5.     this._fadeSprite.opacity = 255;
  6. };
复制代码
具体相关的AudioManager和FadeSprite按照惯例以后再说~

start里的内容都是只需要加载一次的, 而Scene_Title的内容其实实在游戏循环中不断更新的
用户可以一直呆在标题画面, 让bgm循环一遍又一遍都是没问题的
这就是updae周期函数的作用
还记得在SceneManger里讲过的:
start执行一遍后, started标记设置为true, 之后下一帧以后就不会执行start而会执行update

  1. Scene_Base.prototype.update = function() {
  2.     this.updateFade();
  3.     this.updateChildren();
  4. };

  5. Scene_Base.prototype.updateChildren = function() {
  6.     this.children.forEach(function(child) {
  7.         if (child.update) {
  8.             child.update();
  9.         }
  10.     });
  11. };
复制代码

恩, 其实update在Title这里没有什么太多的事情要做
除了淡入淡出的动画需要自己动手以外就没有什么亲自要改变的东西了
对于场景而言, 绝大多数的状态更新是由每个子节点自己决定的
父容器只是帮助他们调用了update方法而已

Scene_Title于是乎就开始等待用户的输入, 来决定下一个Scene是什么
  1. Scene_Title.prototype.commandNewGame = function() {
  2.     DataManager.setupNewGame();
  3.     this._commandWindow.close();
  4.     this.fadeOutAll();
  5.     SceneManager.goto(Scene_Map);
  6. };

  7. Scene_Title.prototype.commandContinue = function() {
  8.     this._commandWindow.close();
  9.     SceneManager.push(Scene_Load);
  10. };

  11. Scene_Title.prototype.commandOptions = function() {
  12.     this._commandWindow.close();
  13.     SceneManager.push(Scene_Options);
  14. };
复制代码
注意一个细节, 在 continue和option这里用的全是push
因为读取界面和选项界面对于Scene_Title是一个子场景, 可以随时返回并保持状态的
而New game则是直接goto的

不过今天就先到这里吧, 下一篇搞什么还没想好, 嗯再说吧~
有点困, 收工




作者: walf_man    时间: 2019-8-21 19:50
辛苦楼主无私奉献,写的很好




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1