add web docs

This commit is contained in:
echo 2017-12-10 20:00:01 +08:00
parent 7e3389884c
commit 3def0dbe8c
33 changed files with 1769 additions and 0 deletions

0
docs/.nojekyll Normal file
View File

12
docs/README.md Normal file
View File

@ -0,0 +1,12 @@
# HTML5 魔塔样板说明文档
众所周知魔塔的趋势是向移动端发展贴吧中也常常能见到“求手机魔塔”的帖子。然而现有的工具中NekoRPG有着比较大的局限性游戏感较差更是完全没法在iOS上运行。而一些APP的魔塔虽然可用但是必须要下载安装对于Android和iOS还必须开发不同的版本非常麻烦。
但是现在我们有了HTML5。 HTML5的画布canvas以及它被Android/iOS内置浏览器所支持的特性可以让我们做出真正意义上的全平台覆盖的魔塔。
事实上在贴吧的试水发布也证明了H5魔塔确实是可以成功的。两部即使是复刻的魔塔也受到了不少人的追捧其流畅的手感和全平台支持的特性也让很多没办法打开电脑的人爱不释手。
然而一般而言使用非RMXP制作魔塔往往需要一定的编程技术HTML5魔塔自然也不例外。但是为了能让大家更加注重于“做塔”本身而不用考虑做塔以外的各种脚本问题我特意制作了这样一部HTML5的魔塔样板。
> 这个魔塔样板可以让你在完全不懂任何编程的情况下做出自己的H5魔塔。不会代码没关系只要你想做就能做出来
下面我将详细地介绍如何使用这一个样板来制作属于自己的HTML5魔塔。

6
docs/_sidebar.md Normal file
View File

@ -0,0 +1,6 @@
- [快速上手](start)
- [元件说明](element)
- [事件](event)
- [个性化](personalization)
- [附录API列表](api)

244
docs/api.md Normal file
View File

@ -0,0 +1,244 @@
# 附录:API列表
所有系统支持的API都列在了这里。可能被做塔时自定义JS脚本中涉及到的以红色字体标出并有着详细的解释。
可以在chrome浏览器的控制台中`ctrl+shift+I`找到Console中直接进行调用以查看效果。
!> `core.js`:系统核心文件。所有核心逻辑处理都在此文件完成。
``` js
core.status.floorId //获得当前层floorId
core.status.thisMap //获得当前层的地图信息
//------ 初始化部分 ------
core.init //初始化
core.showStartAnimate //显示开始界面
core.hideStartAnimate //隐藏开始界面
core.setStartProgressVal //设置加载进度条进度
core.setStartLoadTipText //设置加载进度条提示文字
core.loader //加载图片和音频
core.loadImage //加载图片
core.loadSound //加载音频
core.loadSoundItem //加载某一个音频
core.isPlaying //游戏是否已经开始
core.clearStatus //清除游戏状态和数据
core.resetStatus //重置游戏状态和初始数据
core.startGame //具体开始游戏
core.restart //重新开始游戏;此函数将回到标题页面
//------ 键盘、鼠标事件 ------
core.onKeyDown //按下某个键时
core.onKeyUp //放开某个键时
core.pressKey //按住某个键不动时
core.keyDown //根据按下键的code来执行一系列操作
core.keyUp //根据放开键的code来执行一系列操作
core.ondown //点击(触摸)事件按下时
core.onmove //当在触摸屏上滑动时
core.onup //当点击(触摸)事件放开时
core.getClickLoc //获得点击事件相对左上角的坐标0到12之间
core.onclick //具体点击屏幕上(x,y)点时,执行的操作
core.onmousewheel //滑动鼠标滚轮时的操作(楼层传送时可用滚轮切换楼层)
//------ 自动寻路代码相关 ------
core.clearAutomaticRouteNode //清除自动寻路路线
core.stopAutomaticRoute //停止自动寻路操作
core.continueAutomaticRoute //继续剩下的自动寻路操作
core.clearContinueAutomaticRoute //清除剩下的自动寻路列表
core.setAutomaticRoute //设置一个自动寻路
core.automaticRoute //自动寻路算法,找寻最优路径
core.fillPosWithPoint //显示离散的寻路点
core.clearStepPostfix //清除已经寻路过的部分
//------ 自动行走,行走控制 ------
core.stopAutoHeroMove //停止勇士的自动行走
core.setAutoHeroMove //设置勇士的自动行走路线
core.autoHeroMove //让勇士开始自动行走
core.setHeroMoveInterval //设置行走的效果动画
core.setHeroMoveTriggerInterval //设置勇士行走过程中对途经事件的触发检测
core.turnHero(direction) //设置勇士的方向转向如果指定了direction则会面向该方向否则执行一个转向操作。
core.moveHero //让勇士开始移动
core.moveOneStep //每移动一格后执行的事件。中毒时在这里进行扣血判断。
core.waitHeroToStop(callback) //停止勇士的一切行动等待勇士行动结束后再执行callback回调函数。
core.stopHero //停止勇士的移动状态。
core.drawHero //在hero层绘制勇士。
core.setHeroLoc(name, value) //设置勇士的位置。name为”direction”,”x”,”y”
core.getHeroLoc(name) //获得勇士的位置。
core.nextX //获得勇士面对位置的x坐标
core.nextY //获得勇士面对位置的y坐标
//------ 地图和事件处理 ------
core.openDoor(id, x, y, needKey, callback) //打开一扇位于 (x,y) 的门
core.battle(id, x, y, force, callback) //进行战斗force表示是否强制战斗
core.trigger(x,y) //触发x,y点的事件
core.changeFloor(floorId, stair, heroLoc, time, callback) //楼层切换floorId为目标楼层Idstair可指定为上/下楼梯time动画时间
core.mapChangeAnimate //实际切换的动画效果
core.clearMap //清除地图显示
core.fillText //在某个canvas上绘制一段文字
core.fillRect //在某个canvas上绘制一个矩形
core.strokeRect //在某个canvas上绘制一个矩形的边框
core.setFont //设置某个canvas的文字字体
core.setLineWidth //设置某个canvas的线宽度
core.saveCanvas //保存某个canvas状态
core.loadCanvas //读取某个canvas状态
core.setStrokeStyle //设置某个canvas边框属性
core.setAlpha //设置某个canvas的alpha值
core.setOpacity //设置某个canvas的透明度
core.setFillStyle //设置某个canvas的绘制属性如颜色等
core.drawMap(mapId, callback) //绘制某张地图。mapId为地图Id绘制完毕将执行callback回调函数。
core.noPassExists(x,y) //某个点是否不可通行
core.noPass //某个点是否在区域内且不可通行
core.npcExists(x,y) //某个点是否存在NPC
core.terrainExists(x,y) //某个点是否存在指定的地形
core.stairExists(x,y) //某个点是否存在楼梯
core.nearStair //当前位置是否在楼梯边
core.enemyExists(x,y) //某个点是否存在怪物
core.getBlock(x, y, floorId, needEnable) // 获得某个点的block。floorId指定目标楼层needEnable如果为false则即使该点的事件处于禁用状态也将被返回否则只有事件启用的点才被返回
core.moveBlock //显示移动某块的动画,达到{“type”:”move”}的效果
core.animateBlock //显示/隐藏某个块时的动画效果
core.addBlock //将某个块从禁用变成启用状态
core.removeBlock //将某个块从启用变成禁用状态
core.removeBlockById //根据block的索引删除该块
core.removeBlockByIds //一次性删除多个block
core.addGlobalAnimate //添加一个全局动画
core.removeGlobalAnimate //删除一个或所有全局动画
core.setGlobalAnimate //设置全局动画的显示效果
core.setBoxAnimate //显示UI层某个box的动画如怪物手册中怪物的动画
core.drawBoxAnimate // 绘制UI层的box动画
core.updateFg() //更新全地图的显伤
core.itemCount //获得某个物品的个数
core.hasItem //是否存在某个物品
core.setItem //设置某个物品的个数
core.removeItem //删除某个物品
core.useItem // 使用某个物品直接调用items.js中的useItem函数。
core.canUseItem //能否使用某个物品。直接调用items.js中的canUseItem函数。
core.addItem //增加某个物品的个数
core.getItem //获得某个物品时的事件
core.drawTip //左上角绘制一段提示
core.drawText //地图中间绘制一段文字
//------ 系统机制 ------
core.replaceText //将文字中的${和}(表达式)进行替换
core.calValue //计算表达式的值
core.unshift //向某个数组前插入另一个数组或元素
core.setLocalStorage //设置本地存储
core.getLocalStorage //获得本地存储
core.removeLocalStorage //移除本地存储
core.clone //复制一个对象
core.formatDate //格式化时间为字符串
core.setTwoDigits //两位数显示
core.win //获胜将直接调用events.js中的win函数
core.lose //失败将直接调用events.js中的lose函数
core.debug //进入Debug模式攻防血和钥匙都调成很高的数值
core.checkStatus //判断当前能否进入某个事件
core.openBook //点击怪物手册时的打开操作
core.useFly //点击楼层传送器时的打开操作
core.openToolbox //点击工具栏时的打开操作
core.save //点击保存按钮时的打开操作
core.load //点击读取按钮时的打开操作
core.doSL //实际进行存读档事件
core.syncSave //存档同步操作
core.saveData //存档到本地
core.loadData //从本地读档
core.setStatus //设置勇士属性
core.getStatus //获得勇士属性
core.setFlag //设置某个自定义变量或flag
core.getFlag //获得某个自定义变量或flag
core.hasFlag //是否存在某个自定义变量或flag且值为true
core.insertAction //往当前事件列表之前插入一系列事件
core.lockControl //锁定状态栏,常常用于事件处理
core.unlockControl //解锁状态栏
core.isset //判断某对象是否不为undefined也不会null
core.playSound //播放音频
core.playBgm //播放背景音乐
core.changeSoundStatus //切换声音状态
core.enableSound //启用音效
core.disableSound //禁用音效
core.show //动画显示某对象
core.hide //动画使某对象消失
core.clearStatusBar //清空状态栏
core.updateStatusBar //更新状态栏
core.resize //屏幕分辨率改变后重新自适应
core.resetSize //屏幕分辨率改变后重新自适应
//------ core.js 结束 ------
```
!> `data.js` 定义了一些初始化的数据信息。
!> `enemys.js` 定义了怪物信息。
``` js
core.enemys.getSpecialText //获得特殊属性的文字
core.enemys.getDamage //获得某个怪物的伤害
core.enemys.getCritical //计算某个怪物的临界值
core.enemys.getCriticalDamage //计算某个怪物的临界减伤
core.enemys.getDefDamage //计算某个怪物的1防减伤
core.enemys.calDamage //实际的伤害计算公式
core.enemys.getCurrentEnemys //获得当前层剩下的的怪物列表
```
!> `events.js` 定义了各个事件的处理流程。
``` js
core.events.startGame //开始游戏
core.events.win //获胜
core.events.lose //失败
core.events.checkBlock //检查领域、夹击事件
core.events.afterChangeFloor //楼层切换结束时的事件
core.events.doEvents //开始执行一系列自定义事件
core.events.doAction //执行当前自定义事件列表中的下一个事件
core.events.insertAction //往当前自定义事件列表前插入若干个事件
core.events.openShop //打开一个全局商店
core.events.disableQuickShop //禁用一个快捷商店
core.events.canUseQuickShop //当前能否使用快捷商店
core.events.useItem //尝试使用道具
core.events.afterBattle //战斗结束后触发的事件
core.events.afterOpenDoor //开一个门后触发的事件
core.events.passNet //经过一个路障
core.events.beforeSaveData //即将存档前可以执行的操作
core.events.afterLoadData //读档后,载入事件前可以执行的操作
//------ 界面上的点击事件 ------
core.events.clickAction //自定义事件处理时,对用户点击的处理
core.events.clickBook //怪物手册打开时,对用户点击的处理
core.events.clickFly //楼层传送器打开时,对用户点击的处理
core.events.clickShop //全局商店打开时,对用户点击的处理
core.events.clickQuickShop //快捷商店选项打开时
core.events.clickToolbox //工具栏打开时
core.events.clickSL //存/读档界面打开时
core.events.clickSettings //设置页面打开时
```
!> `maps.js` 定义了地图,以及每个数字所代表的意义。
``` js
core.maps.loadFloor //加载某个楼层(从剧本或存档中)
core.maps.getBlock //将数字替换成实际的内容
core.maps.addEvent //向该楼层添加剧本的自定义事件
core.maps.addChangeFloor //向该楼层添加剧本的楼层转换事件
core.maps.initMaps //初始化所有地图
core.maps.save //将当前地图重新变成数字,以便于存档
core.maps.load //将存档中的地图信息重新读取出来
```
!> `ui.js` 定义了各种界面的绘制。
``` js
core.ui.closePanel //结束一切事件和绘制关闭UI窗口返回游戏进程
core.ui.drawTextBox //绘制一个对话框
core.ui.drawChoices //绘制一个选项界面
core.ui.drawConfirmBox //绘制一个确认/取消的警告页面
core.ui.drawSettings //绘制系统菜单栏
core.ui.drawQuickShop //绘制快捷商店选择栏
core.ui.drawWaiting //绘制一个“请稍后”页面
core.ui.drawSyncSave //绘制存档同步选项
core.ui.drawPagination //绘制分页
core.ui.drawEnemyBook //绘制怪物手册
core.ui.drawFly //绘制楼层传送器
core.ui.drawToolbox //绘制道具栏
core.ui.drawSLPanel //绘制存档/读档界面
core.ui.drawThumbnail //绘制一个缩略图
core.ui.drawAbout //绘制“关于”界面
```

90
docs/element.md Normal file
View File

@ -0,0 +1,90 @@
# 元件说明
在本章中,将对样板里的各个元件进行说明。各个元件主要包括道具、门、怪物、楼梯等等。
请打开样板0层 `sample0.js` 进行参照对比。
![生成地图](./img/sample0.png)
## 道具
本塔目前支持的所有道具列表在样板0层中已全部给出。当你在样板0层中拿到某个宝物时会有提示这里不再赘述详见拿到该道具的说明。
大多数宝物都有默认的效果,十字架和屠龙匕首暂未定义,如有自己的需求可参见[自定义道具效果](./personalization#自定义道具效果)。
!> 请注意,本塔没有"装备"的说法,所有剑盾拿到后将立刻作为攻防数值直接加到勇士的属性上。
拿到道具后将触发`afterGetItem`事件,有关事件的详细介绍请参见[事件](./event)。
如需修改某个道具的效果,在不同区域宝石数据发生变化等问题,请参见[自定义道具效果](./personalization#自定义道具效果)的说明。
## 门
本塔支持6种门黄蓝红绿铁花。前五种门需要有对应的钥匙打开花门只能通过调用`openDoor`事件进行打开。
本塔支持暗墙,但是暗墙也必须通过`openDoor`事件开启。例如样板2层的小偷事件就是可以打开一个暗墙的。
开门后可触发该层的`afterOpenDoor`事件,有关事件的详细介绍请参见第四章。
## 怪物
本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。
如有自己的怪物素材需求请参见[自定义素材](./personalization#自定义素材)的内容。
怪物可以由特殊属性每个怪物最多只能有一个特殊属性。怪物的特殊属性所对应的数字special在下面的`getSpecialText`中定义,请勿对已有的属性进行修改。
![怪物属性](./img/getSpecialText.png)
怪物的伤害计算在下面的`calDamage`函数中,如有自己需求的伤害计算公式请修改该函数的代码。
如果`data.js`中的enableExperience为false即不启用经验的话怪物手册里将不显示怪物的经验值打败怪物也不获得任何经验。
拿到幸运金币后,打怪获得的金币将翻倍。
吸血怪需要在怪物后添加value代表吸血的比例。
![怪物吸血](./img/blood.png)
中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。
衰弱怪让勇士衰弱后,攻防会暂时下降一定的数值(直到衰弱状态解除恢复);这个下降的数值同在`data.js`中的values定义。
![debuff](./img/debuff.png)
诅咒怪将让勇士陷入诅咒状态,诅咒状态下杀怪不获得金币和经验值。
领域怪需要在怪物后添加value代表领域伤害的数值。如果勇士生命值扣减到0则直接死亡触发lose事件。
![怪物属性](./img/domainEnemy.png)
出于游戏性能的考虑,我们不可能每走一步都对领域和夹击进行检查。因此我们需要在本楼层的 checkBlock 中指明哪些点可能会触发领域和夹击事件,在这些点才会对领域和夹击进行检查和处理。
!> 请注意这里的`"x,y"`代表该点的横坐标为x纵坐标为y即从左到右第x列从上到下的第y行从0开始计算
本塔不支持阻击、激光、仇恨、自爆、退化等属性。
(这些属性基本都太恶心了,恶心到都不想加上去)
如有额外需求,可参见[自定义自定义怪物属性](./personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。
### 路障、楼梯、传送门
血网的伤害数值、中毒后每步伤害数值、衰弱时暂时攻防下降的数值,都在 `data.js` 的values内定义。
路障同样会尽量被自动寻路绕过。
有关楼梯和传送门必须在该层样板的changeFloor里指定传送点的目标。
![怪物属性](./img/changefloor.png)
!> 请注意这里的`"x,y"`代表该点的横坐标为x纵坐标为y即从左到右第x列从上到下的第y行从0开始计算。如(6,0)代表最上面一行的正中间一列。
floorId指定的是目标楼层的唯一标识符ID
后面可以写stair到upFloor或downFloor表示将前往目标楼层的上楼梯/下楼梯位置。你也可以写loc然后指定目标点的坐标。
请注意的是如果目标楼层有多个楼梯写stair可能会导致到达的楼梯不确定这时候请使用loc方式来指定具体的点位置。
可以指定direction为up/left/right/down指定后勇士将面向该方向。
可以指定time指定后切换动画时长为指定的数值。
楼梯和传送门默认可`"穿透"`。所谓穿透,就是当寻路穿过一个楼梯/传送门后不会触发楼层传送事件而是继续前进。通过系统Flag可以指定是否穿透你也可以对每个传送点单独设置该项。
![怪物属性](./img/floorset.png)
上面就是整个样板中的各个元件说明。通过这种方式,你就已经可以做出一部没有任何事件的塔了。
尝试着做一个两到三层的塔吧!

880
docs/event.md Normal file
View File

@ -0,0 +1,880 @@
# 事件
本章内将对样板所支持的事件进行介绍。
## 事件的机制
本塔所有的事件都是依靠触发`trigger`完成的。例如,勇士碰到一个门可以触发一个事件`openDoor`,勇士碰到怪物可以触发一个事件`battle`,勇士碰到一个(上面定义的)楼层传送点可以触发一个事件`changeFloor`,勇士穿过路障可以触发一个事件`passNet`,包括勇士到达一个指定的`checkBlock`也可以触发一个检查领域、夹击的事件。上面说的这些事件都是系统本身自带的即类似于RMXP中的公共事件。
``` js
events.prototype.init = function () {
this.events = {
'battle': function (data, core, callback) {
core.battle(data.event.id, data.x, data.y);
if (core.isset(callback))
callback();
},
'getItem': function (data, core, callback) {
core.getItem(data.event.id, 1, data.x, data.y);
if (core.isset(callback))
callback();
},
'openDoor': function (data, core, callback) {
core.openDoor(data.event.id, data.x, data.y, true);
if (core.isset(callback))
callback();
},
'changeFloor': function (data, core, callback) {
var heroLoc = null;
if (core.isset(data.event.data.loc)) {
heroLoc = {'x': data.event.data.loc[0], 'y': data.event.data.loc[1]};
if (core.isset(data.event.data.direction))
heroLoc.direction = data.event.data.direction;
}
core.changeFloor(data.event.data.floorId, data.event.data.stair,
heroLoc, data.event.data.time, callback);
},
'passNet': function (data, core, callback) {
core.events.passNet(data);
if (core.isset(callback))
callback();
},
"checkBlock": function (data, core, callback) {
core.events.checkBlock(data.x, data.y);
if (core.isset(callback))
callback();
},
'action': function (data, core, callback) {
core.events.doEvents(data.event.data, data.x, data.y);
if (core.isset(callback)) callback();
}
}
}
```
上述这些默认的事件已经存在处理机制,不需要我们操心。我们真正所需要关心的,其实只是一个自定义的事件。
!> 本塔中的所有自定义事件能且只能被其他事件触发。不存在RMXP里面那种设置了某个变量为true后一个事件被自动执行的问题。这点和RMXP的区别非常大请务必注意。
所有事件都存在两种状态:启用和禁用。启用状态下,该事件才处于可见状态,可被触发、交互与处理。禁用状态下该事件相当于不存在,不可见、不可被触发、不可交互。所有事件默认情况下都是启用的,除非指定了`enable: false`。在事件列表中使用`type: show`和`type: hide`可以将一个禁用事件启用,或将一个启用事件给禁用。这些都在后面会提到。
## 自定义事件
打开样板1层`sample1.js`)有着一些介绍。下面是更为详细的说明。
所有自定义的事件都是如下的写法:
``` js
"events": { // 该楼的所有可能事件列表
"x, y": {
"trigger": "action", // 触发的trigger, action代表自定义事件
"enable": true, // 该事件初始状态下是否处于启用状态
"data": [ // 实际执行的事件列表
// 事件1
// 事件2
// ...
]
}
}
```
这里的`"x,y"`代表该点的横坐标为`x`,纵坐标为`y`;即从左到右第`x`列,从上到下的第`y`行从0开始计算
我们上面提到,有很多系统已经默认的事件(例如开门、打怪等,相当于公共事件)。如果我们需要自定义一个事件,则需要`"trigger": "action"`,它表示该点是一个自定义事件。
!> 如果系统本身存在事件(如一个怪物),且你指定了`"trigger": "action"`,则原事件会被覆盖。
这种情况下一般需采用后面的afterBattleafterOpenDoor和afterGetItem来进行事件的处理。
如果该点本身不存在系统事件,则`"trigger":"action"`可被省略不写:
``` js
"events": { // 该楼的所有可能事件列表
"x, y": {
// 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略
"enable": true, // 该事件初始状态下是否处于启用状态
"data": [ // 实际执行的事件列表
// 事件1
// 事件2
// ...
]
}
}
```
`"enable": true` 代表该点初始状态下是否是启用的。如果`enable`为`false`,则该点初始状态下禁用,将不会被显示和交互(比如如果该点是个怪,指定了`enable`为`false`,则该怪不会显示在地图上,也不会发生战斗)。
默认情况下`enable`是`true`,所以如果`enable`为`true`,该项也可以省略不写:
``` js
"events": { // 该楼的所有可能事件列表
"x, y": {
// 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略
// 该事件初始状态下是启用状态,则可以省略"enable": true如果是禁用状态则必须加上"enable": false
"data": [ // 实际执行的事件列表
// 事件1
// 事件2
// ...
]
}
}
```
`"data"`为实际执行的事件列表。类似于RMXP中的"脚本",也是由一系列事件顺序构成的(其中可以使用`if和choices`来进行条件判断或用户选择,后面会具体提到)。
如果大括号里只有`"data"`,则可以省略大括号和`"data"`,直接写中括号数组,换句话说,上面和下面这种写法也是等价的,可以进行一下比较:
``` js
"events": { // 该楼的所有可能事件列表
// 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号
"x, y": [ // 实际执行的事件列表
// 事件1
// 事件2
// ...
]
}
```
这种简写方式可以极大方便地造塔者进行造塔。
!> 请注意:如果该点初始的`enable`为`false`,或者该点本身有系统默认事件且需要覆盖(`trigger`),则必须采用上面那种大括号写的方式来定义。
`"data"`中,是由一系列的自定义事件类型组成。每个元素类似于:
``` js
"events": { // 该楼的所有可能事件列表
// 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号
"x, y": [ // 实际执行的事件列表
{"type": "xxx", ...},// 事件1
{"type": "xxx", ...},// 事件2
// ...
// 按顺序写事件,直到结束
]
}
```
`"type"`为该自定义事件的类型;而后面的…则为具体的一些事件参数。
每次,系统都将取出数组中的下一个事件,并进行处理;直到数组中再无任何事件,才会完全结束本次自定义事件,恢复游戏状态。
下面将依次对所有自定义事件类型进行介绍。
### text显示一段文字剧情
使用`{"type": "text"}`可以显示一段文字。后面`"data"`可以指定文字内容。
``` js
"events": { // 该楼的所有可能事件列表
// 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号
"x, y": [ // 实际执行的事件列表
{"type": "text", "data": "在界面上的一段文字"},// 显示文字事件
{"type": "text", "data": "这是第二段文字"},// 显示第二个文字事件
// ...
// 按顺序写事件,直到结束
]
}
```
该项可以简写成直接的字符串的形式,即下面这种方式也是可以的:
``` js
"events": { // 该楼的所有可能事件列表
// 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号
"x, y": [ // 实际执行的事件列表
"在界面上的一段文字",// 直接简写,和下面写法完全等价
{"type": "text", "data": "这是第二段文字"},// 显示第二个文字事件
// ...
// 按顺序写事件,直到结束
]
}
```
所有文字事件均可以进行简写,系统会自动转成`{"type": "text"}`的形式。
另外非常值得注意的一点就是,我们需要手动输入`\n`来进行换行:
``` js
"events": { // 该楼的所有可能事件列表
// 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号
"x, y": [ // 实际执行的事件列表
"在界面上的一段文字",// 直接简写
"这是第一行\n这是第二行\n这是第三行",// 显示第二个文字事件
// ...
// 按顺序写事件,直到结束
]
}
```
我们可以给文字加上标题或图标,只要以`\t[…]`开头就可以,大致共有如下几种情况:
- `\t[hero]` 显示勇士的图标和名字
- `\t[monster_id]`显示某个怪物的图标和名字。`monster_id`在`enemys`中有定义,请前往参照。在`enemys`中有定义,请前往参照。
- 例如:`\t[blackMagician]` 将显示黑暗大法师的图标和名字。
- `\t[名字,npc_id]` 显示某个NPC的名字和图标。`npc_id`所对应的图标具体在`icons.js`中有定义,请前往参照。
- 例如:`\t[小妖精,fairy]` 将显示名字为"小妖精",且是仙子的图标。
- `\t[标题]` 直接显示标题。
- 如果该中括号内只有一项,且不为`hero`也不为某个怪物的ID则会直接显示。如 `\t[你死了]` 直接显示一个标题为"你死了"。
``` js
"x, y": [ // 实际执行的事件列表
"一段普通文字",
"\t[hero]这是一段勇士说的话",
"\t[blackMagician]这是一段黑暗大法师说的话",
"\t[小妖精,fairy]这是一段小妖精说的话,使用仙子(fairy)的图标",
"\t[你赢了]直接显示标题为【你赢了】",
]
```
另外值得一提的是,我们是可以在文字中计算一个表达式的值的。只需要将表达式用 `${ }`整个括起来就可以。
``` js
"x, y": [ // 实际执行的事件列表
"1+2=${1+2}, 4*5+6=${4*5+6}", // 显示"1+2=3, 4*5+6=26"
]
```
我们可以使用 `status:xxx` 代表勇士的一个属性值;`item:xxx` 代表某个道具的个数;`flag:xxx` 代表某个自定义的变量或flag值。
``` js
"x, y": [ // 实际执行的事件列表
"你当前的攻击力是${status: atk}, 防御是${status: def}",
"你的攻防和的十倍是${10*(status: at+status: def)}",
"你的红黄蓝钥匙总数为${item:yellowKey+item:blueKey+item:redKey}",
"你访问某个老人的次数为${flag: man_times}",
]
```
- `status: xxx` 获取勇士属性时只能使用如下几个hp生命值atk攻击力def防御力mdef魔防值money金币experience经验
- `item: xxx` 中的xxx为道具ID。所有道具的ID定义在items.js中请自行查看。例如`item:centerFly` 代表中心对称飞行器的个数。
- `flag: xxx` 中的xxx为一个自定义的变量/Flag如果没有对其进行赋值则默认值为false。
另外,有个小`trick`。是否有时候会觉得不好找到\n换行的位置有一个很简单的方法
你可以用Chrome浏览器打开游戏按Ctrl+Shift+I打开开发者工具找到Console控制台并中输入`core.drawText("…")` 即可立刻看到文字显示的效果。适当添加\n使得显示效果满意后再复制粘贴到你的剧情文本中。
![调试](./img/eventdebug.png)
### setValue设置勇士的某个属性、道具个数或某个变量/Flag的值
`{"type": "setValue"}` 能修改勇士的某个属性、道具个数、或某个自定义变量或`Flag`的值。
其大致写法如下:
使用`setValue`需要指定`name和value`选项。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "setValue", "name": "status: atk", "value": "status:atk+10" } // 攻击提高10点
{"type": "setValue", "name": "status: money", "value": "1000" } // 将金币数设为1000不是+1000
{"type": "setValue", "name": "status: hp", "value": "status:hp*2" } // 生命值翻倍
{"type": "setValue", "name": "item: yellowKey", "value": "item:yellowKey+3" } // 黄钥匙个数加3
{"type": "setValue", "name": "item: boom", "value": "item:boom+10" } // 炸弹个数+10
{"type": "setValue", "name": "flag: man_times", "value": "0" } // 将变量man_times设为0
{"type": "setValue", "name": "flag: man_times", "value": "flag:man_times+2*status:atk" } // 将变量man_times的值加上勇士的攻击数值的两倍
]
```
name为你要修改的属性/道具/Flag每次只能修改一个值。写法和上面完全相同`status:xxx` 表示勇士一个属性item: xxx 表示某个道具个数,`flag:xxx` 表示某个变量或flag值。参见上面的介绍。
value是一个表达式将通过这个表达式计算出的结果赋值给name。该表达式同样可以使用`status:xxx`, `item:xxx`, `flag:xxx`的写法表示勇士当前属性,道具个数和某个变量/Flag值。
### show: 将一个禁用事件启用
我们上面提到了所有事件都必须靠其他事件驱动来完成不存在当某个flag为true时自动执行的说法。那么我们自然要有启用事件的写法。
使用`{"type":"show"}`可以将一个本身禁用的事件启用。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "show", "loc": [3, 6], "floorId": "MT1", "time": 500 } // 启用MT1层[3, 6]位置事件, 动画500ms
{"type": "show", "loc": [3, 6], "time": 500 } // 如果启用目标是当前层则可以省略floorId项
{"type": "show", "loc": [3, 6] } // 如果不指定动画时间则立刻显示否则动画下过逐渐显示time为动画时间
]
```
show事件需要用loc指定目标点的坐标剩下有两个参数floorId和time。
floorId为目标点的楼层如果不是该楼层的事件比如4楼小偷开2楼的门则是必须的如果是当前楼层可以忽略不写。
time为动画效果时间如果指定了某个大于0的数则会以动画效果慢慢从无到有显示动画时间为该数值如果不指定该选项则无动画直接立刻显示。
!> 要注意的是调用show事件后只是让该事件从禁用状态变成启用从不可见不可交互变成可见可交互但本身不会去执行该点的事件。
### hide: 将一个启用事件禁用
`{"type":"hide"}`和show刚好相反它会让一个已经启用的事件被禁用。
其参数和show也完全相同loc指定事件的位置floorId为楼层同层可忽略time指定的话事件会以动画效果从有到无慢慢消失。
但是和show事件有所区别的是loc选项也可以忽略如果忽略loc则使当前事件禁用。即使禁用当前事件也不会立刻结束当前正在进行的而是仍然会依次将列表中剩下的事件执行完
请注意,一次性事件必须要加 `{"type":"hide"}`,尤其是例如走到某个点,触发对话或机关门(陷阱)这种,否则每次都会重复触发。
NPC对话事件结束后如果需要NPC消失也需要调用 `{"type": "hide"}`可以不写loc选项代表当前事件可以指定time使NPC动画消失。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "hide", "loc": [3, 6], "floorId": "MT1", "time": 500 } // 启用MT1层[3, 6]位置事件, 动画500ms
{"type": "hide", "loc": [3, 6], "time": 500 } // 如果启用目标是当前层则可以省略floorId项
{"type": "hide", "loc": [3, 6] } // 如果不指定动画时间则立刻显示否则动画下过逐渐显示time为动画时间
{"type": "hide", "time": 500 } // 如果不指定loc选项则默认为当前点 例如这个就是500ms消失当前对话的NPC
{"type": "hide" } // 无动画将当前事件禁用,常常适用于某个空地点(触发陷阱事件、触发机关门这种)
]
```
### trigger: 立即触发另一个地点的事件
`{"type":"trigger"}` 会立刻触发当层另一个地点的自定义事件。
上面我们说到show事件会让一个禁用事件启用且可被交互但是如果我想立刻让它执行应该怎么办呢使用trigger就行。
其基本写法如下:
``` js
"x, y": [ // 实际执行的事件列表
{"type": "trigger", "loc": [3, 6], }, // 立即触发loc位置的事件当前剩下的事件全部不再执行
"执行trigger后这段文字将不会再被显示"
]
```
其后面带有loc选项代表另一个地点的坐标。
执行trigger事件后当前事件将立刻被结束剩下所有内容被忽略然后重新启动另一个地点的action事件。例如上面这个例子下面的文字将不会再被显示而是直接跳转到`"3,6"`对应的事件列表从头执行。
### revisit: 立即重启当前事件
revisit和trigger完全相同只不过是立刻触发的还是本地点的事件
``` js
"x, y": [ // 实际执行的事件列表
{"type": "revisit" }, // 立即触发本事件,等价于{"type": "trigger", "loc": [3, 6], }
"执行revisit后这段文字将不会再被显示"
]
```
revisit其实是trigger的简写只不过是loc固定为当前点。
revisit常常使用在一些商人之类的地方当用户购买物品后不是离开而是立刻重新访问重新进入购买页面。
### exit: 立刻结束当前事件
上面说到像商人一类购买物品后可以立刻revisit重新访问但是这样就相当于陷入了死循环导致无法离开。
可以使用`{"type":"exit"}`立刻结束事件。调用exit后将立刻结束一切事件清空事件列表并返回游戏。
例如玩家点击商人的"离开"选项则可以调用exit返回游戏。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "exit" }, // 立即结束事件并恢复游戏,一切列表中的事件都将不再被执行
"执行exit后这段文字将不会再被显示"
]
```
#### update: 立刻更新状态栏和地图显伤
当我们在上面调用show事件显示一个怪物后该怪物将不会有显伤显示。如果你需要刷新状态栏和地图显伤只需要简单地调用 `{"type": "update"}` 即可。
### sleep: 等待多少毫秒
等价于RMXP中的"等待x帧",不过是以毫秒来计算。
基本写法:`{"type": "sleep", "time": xxx}` 其中xxx为指定的毫秒数。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "sleep", "time": 1000 }, // 等待1000ms
"等待1000ms后才开始执行这个事件"
]
```
### battle: 强制战斗
调用battle可强制与某怪物进行战斗而无需去触碰到它
例如,《宿命的旋律》中,一区有个骷髅队长,当你拿了它周围三个物品时,就会立刻触发强制战斗事件。这时候就可以用`{"type": "battle"}` 实现。
其基本写法是: `{"type": "battle", "id": xxx}`其中xxx为怪物ID。
``` js
"10,4": [ // 开门后走进去的事件:强制战斗
"\t[blackKing]你终于还是来了。",
"\t[hero]放开我们的公主!",
"\t[blackKing]如果我不愿意呢?",
"\t[hero]无需多说,拔剑吧!",
{"type": "battle", "id": "blackKing"}, // 强制战斗
// 如果战斗失败直接死亡,不会继续触发接下来的剧情。
{"type": "hide", "loc": [10,2]}, // 战斗后需要手动使怪物消失战斗后不会引发afterBattle事件。
{"type": "openDoor", "loc": [8,7]}, // 开门口的机关门
"\t[blackKing]没想到你已经变得这么强大了... 算你厉害。\n公主就交给你了请好好对她。",
{"type": "hide"} // 隐藏本事件
],
```
上面就是样板层中右上角的强制战斗例子。
如果强制战斗失败则会立刻生命归0并死亡调用lose函数接下来的事件不会再被执行。
强制战斗没有指定loc的选项因此战斗后需要调用hide使怪物消失如果有必要。强制战斗不会触发任何afterBattle里的事件。
### openDoor: 开门
调用`{"type":"openDoor"}`可以打开一扇门。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "openDoor", "loc": [3, 6], "floorId": "MT1" }, // 打开MT1层的[3, 6]位置的门
{"type": "openDoor", "loc": [3, 6]}, // 如果是本层则可省略floorId
]
```
loc指定门的坐标floorId指定门所在的楼层ID。如果是当前层则可以忽略floorId选项。
如果loc所在的点是一个墙壁则作为暗墙来开启。
如果loc所在的点既不是门也不是墙壁则忽略忽略本事件。
openDoor不会触发任何afterOpenDoor里的事件。
### changeFloor: 楼层切换
在事件中也可以对楼层进行切换。一个比较典型的例子就是TSW中勇士在三楼的陷阱被扔到了二楼就是一个楼层切换事件。
changeFloor的事件写法大致如下。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "changeFloor", "floorId": "sample0""loc": [10, 10], "direction": "left", "time": 1000 }, //后面几项依次为楼层id楼层位置不能放置在楼梯位置两项为必填勇士方向可选切换时间也是可选。
]
```
可以看到,与上面的楼梯、传送门的写法十分类似。
但是相比那个而言不支持stair楼梯位置只能写坐标没有穿透选项。
direction为可选的指定的话将使勇士的朝向变成该方向
time为可选的指定的话将作为楼层切换动画的时间。
### changePos: 当前位置切换
有时候我们不想要楼层切换的动画效果而是直接让勇士从A点到B点。
这时候可以用changePos。其参数和changeFloor类似但少了floorId和time两个选项。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "changePos", "loc": [10, 10], "direction": "left"}, // 直接切换勇士的坐标loc为目标地点必填后面勇士换位后方向可选
]
```
### openShop: 打开一个全局商店
使用openShop可以打开一个全局商店。有关全局商店的说明可参见[全局商店](#全局商店)。
### move: 让某个NPC/怪物移动
如果我们需要移动某个NPC或怪物可以使用`{"type": "move"}`。
下面是该事件常见的写法:
``` js
"x, y": [ // 实际执行的事件列表
{"type": "move", "time": 750, "steps": [// 动画效果time为移动速度(比如这里每750ms一步)steps为移动数组
{"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步在向下移动一步并消失
"down" // 如果该方向上只移动一步则可以这样简写效果等价于上面value为1
], "immediateHide": true }, //最后这项可选制定为true则立刻消失否则渐变消失
]
```
time选项必须指定为每移动一步所需要用到的时间。
steps为一个数组其每一项为一个 `{"direction" : xxx, "value": n}`表示该步是向xxx方向移动n步。
如果只移动一步可以直接简单的写方向字符串(`up/left/down/right`)。
immediateHide为一个可选项代表该事件移动完毕后是否立刻消失。如果该项指定了并为true则移动完毕后直接消失否则以动画效果消失。
值得注意的是当调用move事件时实际上是使事件脱离了原始地点。为了避免冲突规定move事件会自动调用hide事件。
换句话说当move事件被调用后该点本身的事件将被禁用。
move完毕后移动的NPC/怪物一定会消失只不过可以通过immediateHide决定是否立刻消失还是以time作为时间来动画效果消失。
如果想让move后的NPC/怪物仍然可以被交互,需采用如下的写法:
在移动的到达点指定一个初始禁用的相同NPC然后move事件中指定immediateHide使立刻消失并show该到达点坐标使其立刻显示看起来就像没有消失然后就可以触发目标点的事件了。
### playSound: 播放音效
使用playSound可以立刻播放一个音效。
使用方法:`{"type": "playSound", "name": "item.ogg"}`
值得注意的是如果是额外添加进文件的音效则需在main.js中this.sounds里加载它。
由于考虑到用户流量问题每个额外音效最好不超过20KB。
### win: 获得胜利
`{"type": "win", "reason": "xxx"}` 将会直接调用events.js中的win函数并将reason作为参数传入。
该事件会显示获胜页面,并重新游戏。
### lose: 游戏失败/Game Over
`{"type": "lose", "reason": "xxx"}` 将会直接调用`events.js`中的lose函数并将reason作为参数传入。
该事件会显示失败页面,并重新开始游戏。
### if: 条件判断
使用`{"type": "if"}`可以对条件进行判断,根据判断结果将会选择不同的分支执行。
其大致写法如下:
``` js
"x, y": [ // 实际执行的事件列表
{"type": "if", "condition": "...", // 测试某个条件
"true": [ // 条件成立则执行true里面的事件
],
"false": [ // 条件不成立则执行false里的事件
]
},
]
```
我们可以在condition中给出一个表达式能将`status:xxx, item:xxx, flag:xxx`来作为参数),并进行判断是否成立。
如果条件成立,则将继续执行`"true"`中的列表事件内容。
如果条件不成立,则将继续执行`"false"`中的列表事件内容。
例如下面这个例子每次将检查你的攻击力是否大于500不是的场合将给你的攻击力加100点。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "if", "condition": "status:atk>500", // 判断攻击力是否大于500
"true": [ // 条件成立则执行true里面的事件
"你的攻击力已经大于500了",
{"type": "exit"} // 结束
],
"false": [ // 条件不成立则执行false里的事件
"你当前攻击力为${status:atk}, 不足500\n给你增加100点攻击力",
{"type": "setValue", "name": "status:atk", "value": "status:atk+100"}, //攻击力加100 接着会执行revisit事件
]
},
{"type", "revisit"}, //立刻重启本事件, 直到攻击力大于500后结束
]
```
需要额外注意的几点:
- 给定的表达式condition一般需要返回true或false。
- `flag:xxx` 可取用一个自定义变量或flag。如果从未设置过该flag则其值默认为false。而JS中`false==0`这个判断是成立的,因此我们可以简单使用 `"flag:npc_times==0"` 来判断某个NPC是否被访问过。
- 即使成功失败的场合不执行事件对应的true或false数组也需要存在不过简单的留空就好。
- if可不断嵌套一层套一层如成立的场合再进行另一个if判断等。
- if语句内的内容执行完毕后将接着其后面的语句继续执行。
### choices: 给用户提供选项
choices是一个很麻烦的事件它将弹出一个列表供用户进行选择。
当用户做出了不同的选择,可以有着不同的分支处理。
其完全类似于RMXP中的"显示选择项""XX的场合",只不过同样是需要使用数组来定义。
其大致写法如下:
``` js
"x, y": [ // 实际执行的事件列表
{"type": "choices", "text": "...", // 提示文字
"choices": [{
"text": "选项1文字", "action": [
// 选项1执行的事件
]},
"text": "选项2文字", "action": [
// 选项2执行的事件
]},
"text": "选项3文字", "action": [
// 选项3执行的事件
]},
],
},
]
```
其中最外面的"text"为提示文本。同上面的`"type":"text"`一样,支持`${}`表达式的计算,和\t显示名称、图标。text可省略如果省略将不显示任何提示文字。
choices为一个数组其中每一项都是一个选项列表。
每一项的text为显示在屏幕上的选项名也支持${}的表达式计算,但不支持`\t[]`的显示。action为当用户选择了该选项时将执行的事件。
选项可以有任意多个但一般不要超过6个否则屏幕可能塞不下。
下面是一个卖钥匙的事件是一个比较复杂却也较为典型的if和choices合并使用的样例。
``` js
"10,11": [ // 商人事件if语句和choices语句的写法
// 这部分逻辑相对比较长,细心看,很容易看懂的。
{"type": "if", "condition": "flag:woman_times==0", // 条件判断:是否从未访问过此商人。
"true": [ // 如果从未访问过该商人,显示一段文字
"\t[老人,woman]这是个很复杂的例子,它将教会你如何使用\nif 语句进行条件判断,以及 choices 提供\n选项来供用户进行选择。",
"\t[老人,woman]第一次访问我将显示这段文字;从第二次开始\n将会向你出售钥匙。\n钥匙价格将随着访问次数递增。\n当合计出售了七把钥匙后将送你一把大黄门\n钥匙并消失不再出现。",
"\t[老人,woman]这部分的逻辑比较长,请细心看样板的写法,\n是很容易看懂并理解的。"
// 第一次访问结束
],
"false": [ // 如果已经访问过该商人
{"type": "if", "condition": "flag:woman_times==8", // 条件判断:是否已经出售七把钥匙
"true": [ // 如果已经出售过七把钥匙,则直接结束
"\t[老人,woman]你购买的钥匙已经够多了,再继续卖给你的话\n我会有危险的。",
"\t[老人,woman]看在你贡献给我这么多钱的份上,送你一把大\n黄门钥匙吧希望你能好好用它。",
{"type": "setValue", "name": "item:bigKey", "value": "item:bigKey+1"}, // 获得一把大黄门钥匙
"\t[老人,woman]我先走了,拜拜~",
{"type":"hide", "time": 500}, // 消失
{"type":"exit"} // 立刻结束当前事件。下面的 setValue 和 revisit 都不会再执行。
],
"false": [ // 否则,显示选择页面
{"type": "choices", "text": "\t[老人,woman]少年,你需要钥匙吗?\n我这里有大把的", // 显示一个选择页面
"choices": [ // 提供四个选项:黄钥匙、蓝钥匙、红钥匙、离开。前三个选项显示需要的金额
{"text": "黄钥匙(${9+flag:woman_times}金币)", "action": [ // 第一个选项,黄钥匙
// 选择该选项的执行内容
// 条件判断:钱够不够
{"type": "if", "condition": "status:money>=9+flag:woman_times",
"true": [
{"type": "setValue", "name": "status:money", "value": "status:money-(9+flag:woman_times)"}, // 扣减金钱
{"type": "setValue", "name": "item:yellowKey", "value": "item:yellowKey+1"}, // 增加黄钥匙
// 然后会继续执行下面的setValue来增加商人访问次数
],
"false": [
"\t[老人,woman]你的金钱不足!",
{"type": "revisit"} // 直接重新访问不执行下面的setValue来增加访问次数
]
}
]},
{"text": "蓝钥匙(${18+2*flag:woman_times}金币)", "action": [ // 第二个选项:蓝钥匙
// 逻辑和上面黄钥匙完全相同,不赘述
{"type": "if", "condition": "status:money>=18+2*flag:woman_times",
"true": [
{"type": "setValue", "name": "status:money", "value": "status:money-(18+2*flag:woman_times)"},
{"type": "setValue", "name": "item:blueKey", "value": "item:blueKey+1"},
],
"false": [
"\t[老人,woman]你的金钱不足!",
{"type": "revisit"}
]
}
]},
{"text": "红钥匙(${36+4*flag:woman_times}金币)", "action": [ // 第三个选项:红钥匙
// 逻辑和上面黄钥匙完全相同,不赘述
{"type": "if", "condition": "status:money>=36+4*flag:woman_times",
"true": [
{"type": "setValue", "name": "status:money", "value": "status:money-(36+4*flag:woman_times)"},
{"type": "setValue", "name": "item:redKey", "value": "item:redKey+1"},
],
"false": [
"\t[老人,woman]你的金钱不足!",
{"type": "revisit"}
]
}
]},
{"text": "离开", "action": [ // 第四个选项:离开
{"type": "exit"} // 立刻结束当前事件
]}
]
}
]
}
]
},
{"type": "setValue", "name": "flag:woman_times", "value": "flag:woman_times+1"}, // 增加该商人的访问次数。
{"type": "revisit"} // 立即重新开始这个事件
],
```
### function: 自定义JS脚本
上述给出了这么多事件,但有时候往往不能满足需求,这时候就需要执行自定义脚本了。
``` js
"x, y": [ // 实际执行的事件列表
{"type": "function", "function": function(){ // 执行一段js脚本
// 这里写js代码
alert(core.getStatus("atk")); // 弹窗显示勇士的攻击力
}},
]
```
`{"type":"function"}`需要有一个`"function"`参数它是一个JS函数里面可以写任何自定义的JS脚本系统将会执行它。
系统所有支持的API都在附录中给出。
这里只简单列出给一些最常见的API
``` js
core.getStatus(name) //获得勇士的某个属性hp/atk/def/…)
core.setStatus(name, value) //设置勇士某个属性值为value
core.itemCount(name) //获得某个道具的个数
core.getItem(name, count) //获得某个道具count个
core.setItem(name, value) //设置某个道具为value个
core.getFlag(name, defaultValue) //获得某个自定义变量flag如果未定义则返回defaultValue
core.setFlag(name, value) //将某个自定义变量/flag设置为value
core.hasFlag(name) //判断某个自定义flag是否成立。只有被被赋值过且不为0或false时才会返回true。
core.updateStatusBar() //立刻更新状态栏和地图显伤。上面各种get和set均不会对状态栏和地图显伤更新需要手动调用这个函数。
core.insertAction(list) //往当前事件列表中插入一系列事件。使用这个函数插入的事件将在这段自定义JS脚本执行完毕后立刻执行。
// ……
```
## 全局商店
我们可以采用上面的choices方式来给出一个商店。这样的商店确实可以有效地进行操作但是却是"非全局"的换句话说只有在碰到NPC的时候才能触发商店事件。
我们可以定义"全局商店",其可以直接被快捷栏中的"快捷商店"进行调用。换句话说,我们可以定义快捷商店,让用户在任意楼层都能快速使用商店。
全局商店定义在`data.js`中找到shops一项。
``` js
"shops": { // 定义全局商店(即快捷商店)
"moneyShop1": { // 商店唯一ID
"name": "贪婪之神", // 商店名称(标题)
"icon": "blueShop", // 商店图标blueShop为蓝色商店pinkShop为粉色商店
"textInList": "3楼金币商店", // 在快捷商店栏中显示的名称
"use": "money", // 商店所要使用的。只能是"money"或"experience"。
"need": "20+10*times*(times+1)", // 商店需要的金币/经验数值可以是一个表达式以times作为参数计算。
// 这里用到的times为该商店的已经的访问次数。首次访问该商店时times的值为0。
// 上面的例子是50层商店的计算公式。你也可以写任意其他的计算公式只要以times作为参数即可。
// 例如: "need": "25" 就是恒定需要25金币的商店 "need": "20+2*times" 就是第一次访问要20金币以后每次递增2金币的商店。
// 如果是对于每个选项有不同的计算公式,写 "need": "-1" 即可。可参见下面的经验商店。
"choices": [ // 商店的选项
{"text": "生命+800", "effect": "status:hp+=800"},
// 如果有多个effect以分号分开参见下面的经验商店
{"text": "攻击+4", "effect": "status:atk+=4"},
{"text": "防御+4", "effect": "status:def+=4"},
{"text": "魔防+10", "effect": "status:mdef+=10"}
// effect只能对status和items进行操作不能修改flag值。
// 中间只能用+=符号(也就是只能增加某个属性或道具)
// 其他effect样例
// "items:yellowKey+=1" 黄钥匙+1
// "items:pickaxe+=3" 破墙镐+3
]
},
"expShop1": { // 商店唯一ID
"name": "经验之神",
"icon": "pinkShop",
"textInList": "5楼经验商店",
"use": "experience", // 该商店使用的是经验进行计算
"need": "-1", // 如果是对于每个选项所需要的数值不同,这里直接写-1然后下面选项里给定具体数值
"choices": [
// 在choices中写need可以针对每个选项都有不同的需求。
// 这里的need同样可以以times作为参数比如 "need": "100+20*times"
{"text": "等级+1", "need": "100", "effect": "status:hp+=1000;status:atk+=7;status:def+=7"},
// 多个effect直接以分号分开即可。如上面的意思是生命+1000攻击+7防御+7。
{"text": "攻击+5", "need": "30", "effect": "status:atk+=5"},
{"text": "防御+5", "need": "30", "effect": "status:def+=5"},
]
}
},
```
全局商店全部定义在`data.js`中的shops一项里。
每个全局商店有一个唯一标识符ID然后是一系列对该商店的定义。
- name为商店的名称打开商店后的标题
- icon 为商店的图标blueShop为蓝色商店pinkShop为粉色商店
- textInList 为其在快捷商店栏中显示的名称,如"3楼金币商店"等
- use 为消耗的类型是金币money还是经验experience
- need 是一个表达式,计算商店所需要用到的数值。
- 可以将times作为参数times为该商店已经访问过的次数第一次访问时times是0。
- 如果对于每个选项都需要不同的数值,这里设为"-1";可参见下面经验商店的例子。
- choices 为商店的各个选项是一个list每一项是一个选项
- text为显示文字。请注意这里不支持 ${} 的表达式计算。
- effect 为该选项的效果effect只能对status或items进行操作且必须是 `status:xxx+=yyy``item:xxx+=yyy`的形式。即中间必须是+=符号。
- 如有多个effect例如升级全属性提升使用分号分开参见经验商店的写法。
像这样定义了全局商店后,即可在快捷栏中看到。
请注意,快捷商店默认是不可被使用的。直到至少调用一次自定义事件中的 `{"type": "openShop"}` 打开商店后,才能真正在快捷栏中被使用。
## 系统引发的自定义事件
我们知道,所有自定义事件都是需要定义在`"x,y"`处,并且得让用户经过或撞上才能触发的。
但是有一系列的事件,例如战斗、获取道具、开门等,是系统已经预先设定好的事件,我们不能将其覆盖为自定义事件,否则原本的战斗等事件会被覆盖。
为了解决此问题,在每层的剧本中引入了三个元素:`afterBattle`, `afterGetItem`, `afterOpenDoor`
当某个战斗结束后,将执行`afterBattle`中,对应位置的事件。
当获取某个道具后,将执行`afterGetItem`中,对应位置的事件。
当开了某个门后,将执行`afterOpenDoor`中,对应位置的事件。
例如,下面就是一个典型的杀怪开门的例子。每当杀死一个守卫机关门的怪物,将检查是否满足打开机关门的条件。如果是,则开启机关门。
``` js
"afterBattle": { // 战斗后可能触发的事件列表
"9,6": [ // 初级卫兵1
{"type": "setValue", "name": "flag:door", "value": "flag:door+1"}, // 将"door"这个自定义flag加一
{"type": "if", "condition": "flag:door==2", // 一个条件判断事件,条件是"door"这个flag值等于2
"true": [ // 如果条件成立:打开机关门
{"type": "openDoor", "loc": [10,5]}
],
"false": [] // 如果条件不成立则无事件触发
},
],
"11,6": [ // 初级卫兵2注意由于打怪顺序问题可能都得写一遍。
{"type": "setValue", "name": "flag:door", "value": "flag:door+1"}, // 将"door"这个自定义flag加一
{"type": "if", "condition": "flag:door==2", // 一个条件判断事件,条件是"door"这个flag值等于2
"true": [ // 如果条件成立:打开机关门
{"type": "openDoor", "loc": [10,5]}
],
"false": [] // 如果条件不成立则无事件触发
},
],
},
```
同样为了实现类似于RMXP中到达某一层后自动触发某段事件的效果样板中还存在`firstArrive`事件。当且仅当勇士第一次到达某层时,将会触发此事件。可以利用此事件来显示一些剧情,或再让它调用 `{"type": "trigger"}` 来继续调用其他的事件。
## 开始,难度分歧,获胜与失败
游戏开始时将调用`events.js`中的startGame函数。
它将显示`data.js`中的startText内容可以修改成自己的并正式开始游戏。
其参数hard为以下三个字符串之一`"Easy"`, `"Normal"`, `"Hard"`,分别对应三个难度。针对不同的难度,我们可以设置一些难度分歧。
``` js
////// 游戏开始事件 //////
events.prototype.startGame = function (hard) {
if (core.status.isStarting) return;
core.status.isStarting = true;
core.hideStartAnimate(function() {
core.drawText(core.clone(core.firstData.startText), function() {
core.startGame(hard);
if (hard=='Easy') { // 简单难度
core.setFlag('hard', 1); // 可以用flag:hard来获得当前难度
// 可以在此设置一些初始福利,比如设置初始生命值可以调用:
// core.setStatus("hp", 10000);
}
if (hard=='Normal') { // 普通难度
core.setFlag('hard', 2); // 可以用flag:hard来获得当前难度
}
if (hard=='Hard') { // 困难难度
core.setFlag('hard', 3); // 可以用flag:hard来获得当前难度
}
});
})
}
```
难度会赋值给`flag:hard`即我们可以在上述if语句的condition中进行判断。
请不要在任何事件中修改对`flag:hard`进行任何赋值(修改它),不然可能会造成一些不可知的后果。
当获胜`{"type": "win"}`事件发生时,将调用`events.js`中的win事件。其显示一段恭喜文字并重新开始游戏。
``` js
////// 游戏结束事件 //////
events.prototype.win = function(reason) {
// 获胜
core.waitHeroToStop(function() {
core.removeGlobalAnimate(0,0,true);
core.clearMap('all'); // 清空全地图
core.drawText([
"\t[结局2]恭喜通关!你的分数是${status:hp}。"
], function () {
core.restart();
})
});
}
```
其参数reason为获胜原因即type:win事件里面的reason参数
你可以在这里修改自己的获胜界面显示的文字。
当失败(`{"type": "lose"}`,或者被怪强制战斗打死、被领域怪扣血死、中毒导致扣血死,路障导致扣血死等等)事件发生时,将调用`events.js`中的`lose`事件。其直接显示一段文字,并重新开始游戏。
``` js
events.prototype.lose = function(reason) {
// 失败
core.waitHeroToStop(function() {
core.drawText([
"\t[结局1]你死了。\n如题。"
], function () {
core.restart();
});
})
}
```
其参数reason为失败原因。
你可以在这里修改失败界面时显示的文字。

BIN
docs/img/blood.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/img/changefloor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/img/dataInit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/img/debuff.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/img/domainEnemy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/img/enemyArray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
docs/img/eventdebug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
docs/img/flag1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/img/flag2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/img/floordata.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/img/floorset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/img/getSpecialText.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

BIN
docs/img/map1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
docs/img/mapArray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/img/mapmean.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/img/modData.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/img/rmxp1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/img/rmxp2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
docs/img/rmxp3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
docs/img/sample0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
docs/img/save.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
docs/img/script.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

45
docs/index.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="shortcut icon" type="image/png" href="./img/logo.png">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
loadSidebar: true,
name: 'H5魔塔样板',
repo: 'https://github.com/ckcz123/mota-js',
// Search Support
// search: {
// maxAge: 43200000, // 过期时间,单位毫秒,默认一天
// paths: 'auto',
// placeholder: {
// '/en/': 'Search',
// '/': '搜索',
// },
// noData: {
// '/en/': 'No Results',
// '/': '找不到结果',
// },
// },
// load sidebar from _sidebar.md
loadSidebar: '_sidebar.md',
subMaxLevel: 2,
autoHeader: true,
}
// 离线模式
// if (typeof navigator.serviceWorker !== 'undefined') {
// navigator.serviceWorker.register('serviceWorker.js')
// }
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
</body>
</html>

298
docs/personalization.md Normal file
View File

@ -0,0 +1,298 @@
# 个性化
有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。
## 自定义素材
所有素材的图片都在`images`目录下。
`animates.png` 为所有动画效果。主要是星空熔岩,开门,毒网,传送门之类的效果。为四帧。
`enemys.png` 为所有怪物的图片。地图生成器中对应的数字从上至下依次是会从201开始计算绿色史莱姆为201小蝙蝠为205依次类推。请注意动画效果为两帧一般是原始四帧中的1和3。四帧中12相同34相同因此只取1和3即可
`heros.png`为勇士行走图。这里是`4*3`的,你也可以用`4*4`的,不过需要一些修改,之后会提到。
`items.png` 为所有道具的图标。
`npcs.png` 为所有NPC的图标也是两帧。
`terrains.png` 为所有地形的图标。
系统会读取`icon.js`文件并获取每个ID对应的图标所在的位置。
### 地图生成器使用自定义素材
地图生成器可以将数字和图标一一对应,从数字生成图标或从图标生成数字。
在使用自定义素材后,我们可以使用地图生成器来识别新的素材。打开同目录下的`meaning.txt`,按照已有的方式来增加或编辑内容即可。
第一列是地图生成器中的数字,第二列是它所在的文件名,第三列是坐标。
![地图含义](./img/mapmean.png)
### 使用自定义地形(路面、墙壁等)
你需要自行P一张图里面是你需要的地形素材。然后将其覆盖`terrains.png`文件,请注意所有元件的位置需要完全相同对应。
岩浆、星空、传送门、箭头、门的开启动画、暗墙的开启动画在`animates.png`中,可能也需要对应进行修改。
请打开`items.js`中,对比素材的位置。
另外需要注意的一点是,本样板不支持`Autotile`,换句话说野外地图(草地等)这种自然风可能不会太适合。
### 使用自定义道具图标
如果你觉得已有的道具图标不够,想自己添加图标也是可以的。
如果`items.png`中不存在你需要的图标则可以自己P一张图将你需要的图标覆盖到某个用不到的图标上。或者也可以接着后面向下拉伸。
所有道具必须是`32x32`像素。
P图完毕后可以在地图生成器中加入对应的数字和图标的对应关系。
要在系统中启用你的图标你需要自己指定一个道具的ID不能和任何已有的重名然后进行如下操作
- 在`items.js`中道具的定义列表中编辑,修改你的自定义道具的名称,类型,说明文字等。
- 即捡即用类道具的cls为items消耗类道具的cls为tools永久类道具的cls为constants。
- 在`icon.js`中找到items一栏往里面添加你的图标位置。
- 在`maps.js`中,找到对应位置,往里面添加自己的数字和道具的一一对应关系。(该数字可任意指定,不能和已有的冲突),类似下面这样
``` js
if (id == 60) tmp.event = {'cls': 'items', 'id': 'curseWine'} // 解咒药水
if (id == 61) tmp.event = {'cls': 'items', 'id': 'superWine'} // 万能药水
if (id == 62) tmp.event = {'cls': 'items', 'id': 'knife'} // 屠龙匕首
if (id == 63) tmp.event = {'cls': 'items', 'id': 'moneyPocket'} // 金钱袋
if (id == 64) tmp.event = {'cls': 'items', 'id': 'shoes'} // 绿鞋
if (id == 65) tmp.event = {'cls': 'items', 'id': 'hammer'} // 圣锤
```
有关如何自行实现一个道具的效果,参见[自定义道具效果](#自定义道具效果)。
### 使用自定义怪物图标
如果你觉得已有的怪物图标不够,也可以自行向后添加你需要的怪物。
在`enemys.js`中直接向后P图并将你需要的怪物的两帧动画放到正确的位置注意普通四帧动画的1和3帧
P图完毕后可以在地图生成器中加入对应的数字和图标的对应关系。
要在系统中启用你的图标你需要自己指定一个怪物的ID不能和任何已有的重名然后进行如下操作
1. 在`enemys.js`中,向怪物的定义列表中,添加一个怪物。
2. 在`icon.js`中找到enemys一栏往里面添加你的图标位置。
3. 在`maps.js`中,找到对应位置,往里面添加自己的数字和怪物的一一对应关系。一般对于怪物而言,对应的数字就是`200+`位置。
有关如何自行实现一个怪物的特殊属性或伤害计算公式,参见[怪物的特殊属性](#怪物的特殊属性)。
### 使用自定义NPC图标
同上你也可以自定义NPC图标。只需要将你NPC的两帧动画接到`npcs.js`中即可。
P图完毕后可以在地图生成器中加入对应的数字和图标的对应关系。
要在系统中启用你的图标你需要自己指定一个NPC的ID不能和任何已有的重名然后进行如下操作
1. 在`icon.js`中找到npcs一栏往里面添加你的图标位置。
2. 在`maps.js`中找到对应位置往里面添加自己的数字和NPC的一一对应关系。
``` js
// 121-150 NPC
if (id == 121) tmp.event = {'cls': 'npcs', 'id': 'man'};
if (id == 122) tmp.event = {'cls': 'npcs', 'id': 'woman'};
if (id == 123) tmp.event = {'cls': 'npcs', 'id': 'thief'};
if (id == 124) tmp.event = {'cls': 'npcs', 'id': 'fairy'};
if (id == 125) tmp.event = {'cls': 'npcs', 'id': 'magician'};
if (id == 126) tmp.event = {'cls': 'npcs', 'id': 'womanMagician'};
```
### 使用自定义勇士图标
我们同样可以使用自定义的勇士图标,比如小可绒之类。
拿一个勇士的行走图覆盖`hero.png`即可。
请注意:勇士必须是`32x32`像素,不能使用超过`32x32`的图片。
覆盖了`hero.png`后,我们需要在`icons.js`中编辑图标的位置信息。
``` js
'heros': {
'hero1': {
'down': {'loc': 0, 'stop': 0, 'leftFoot': 1, 'rightFoot': 2},
'left': {'loc': 1, 'stop': 0, 'leftFoot': 1, 'rightFoot': 2},
'right': {'loc': 2, 'stop': 0, 'leftFoot': 1, 'rightFoot': 2},
'up': {'loc': 3, 'stop': 0, 'leftFoot': 1, 'rightFoot': 2}
}
},
```
hero1为勇士ID和`data.js`中的ID对应一般不修改。
接着`down/left/right/up`是勇士四个朝向每个朝向中的loc表示是在`hero.png`中的第几行后面的stopleftFoorrightFoot分别是停止图左脚行走图和右脚行走图在该行的第几列就行。
我们只需要覆盖图片后编辑这里即可。即使是`4x4`的行走图,也是没问题的(需要指定左脚和右脚所在的第几列)。
通过上述这几种方式,我们可以修改素材图片(使用自定义素材),指定数字并放入地图生成器中,然后在系统中进行启用。
!> 请注意:强制要求所有素材都必须是`32x32`的,不然可能会造成不可预料的后果。
## 自定义道具效果
本节中将继续介绍如何自己编辑一个道具的效果。
道具效果的具体实现都在`items.js`中。
### 即捡即用类道具cls: items
对于即捡即用类道具,如宝石、血瓶、剑盾等,我们可以简单地修改`data.js`中的value一栏即可。
如果你有更高级的需求(例如每个区域的效果不同),则需要编辑`items.js`文件。具体方式是:
1. 找到`getItemEffect`函数;所有即捡即用类道具的效果都在这里实现。
2. 算道具效果系数,或应该增加的值。
3. 修改同样修改下面的`getItemEffectTip`函数,使提示文字相应变动。
``` js
items.prototype.getItemEffect = function(itemId, itemNum) {
var itemCls = core.material.items[itemId].cls;
// 消耗品
if (itemCls === 'items') {
if (itemId === 'redJewel') core.status.hero.atk += core.values.redJewel;
if (itemId === 'blueJewel') core.status.hero.def += core.values.blueJewel;
if (itemId === 'greenJewel') core.status.hero.mdef += core.values.greenJewel;
if (itemId == 'yellowJewel') { // 黄宝石属性:需自己定义
core.status.hero.hp+=1000;
core.status.hero.atk+=6;
core.status.hero.def+=6;
core.status.hero.mdef+=10;
}
if (itemId === 'redPotion') core.status.hero.hp += core.values.redPotion;
if (itemId === 'bluePotion') core.status.hero.hp += core.values.bluePotion;
if (itemId === 'yellowPotion') core.status.hero.hp += core.values.yellowPotion;
if (itemId === 'greenPotion') core.status.hero.hp += core.values.greenPotion;
if (itemId === 'sword1') core.status.hero.atk += core.values.sword1;
if (itemId === 'sword2') core.status.hero.atk += core.values.sword2;
if (itemId == 'sword3') core.status.hero.atk += core.values.sword3;
if (itemId == 'sword4') core.status.hero.atk += core.values.sword4;
if (itemId === 'sword5') core.status.hero.atk += core.values.sword5;
if (itemId === 'shield1') core.status.hero.def += core.values.shield1;
if (itemId === 'shield2') core.status.hero.def += core.values.shield2;
if (itemId === 'shield3') core.status.hero.def += core.values.shield3;
if (itemId === 'shield4') core.status.hero.def += core.values.shield4;
if (itemId === 'shield5') core.status.hero.def += core.values.shield5;
if (itemId === 'bigKey') { // 只有是钥匙盒才会执行这一步
core.status.hero.items.keys.yellowKey++;
core.status.hero.items.keys.blueKey++;
core.status.hero.items.keys.redKey++;
}
if (itemId == 'superPotion') core.status.hero.hp *= 2;
if (itemId == 'moneyPocket') core.status.hero.money += core.values.moneyPocket;
}
else {
core.addItem(itemId, itemNum);
}
}
```
!> 请注意这里`core.status.thisMap.name`获取的是当前层中你在剧本文件里写的name那一项即状态栏中的层数显示。然后可以通过几个简单的if来判断应该增加的值。
### 消耗类道具cls: tools永久类道具cls: constants
如果要自己实现消耗类道具或永久类道具的使用效果,则需修改`items.js`中的canUseItem和useItem两个函数。
具体过程比较复杂需要一定的JS能力在这里就不多说了有需求可以找艾之葵进行了解。
但值得一提的是,我们可以使用`core.hasItem(name)` 来判断是否某个道具是否存在。例如下面是passNet通过路障处理的一部分
``` js
/****** 经过路障 ******/
events.prototype.passNet = function (data) {
// 有鞋子
if (core.hasItem('shoes')) return;
if (data.event.id=='lavaNet') { // 血网
core.status.hero.hp -= core.values.lavaDamage;
if (core.status.hero.hp<=0) {
core.status.hero.hp=0;
core.updateStatusBar();
core.events.lose('lava');
return;
}
core.drawTip('经过血网,生命-'+core.values.lavaDamage);
}
if (data.event.id=='poisonNet') { // 毒网
if (core.hasFlag('poison')) return;
core.setFlag('poison', true);
}
if (data.event.id=='weakNet') { // 衰网
if (core.hasFlag('weak')) return;
core.setFlag('weak', true);
core.status.hero.atk-=core.values.weakValue;
core.status.hero.def-=core.values.weakValue;
}
if (data.event.id=='curseNet') { // 咒网
if (core.hasFlag('curse')) return;
core.setFlag('curse', true);
}
core.updateStatusBar();
}
```
我们进行了一个简单的判断,如果拥有绿鞋,则不进行任何路障的处理。
### 实战!拿到神圣盾后免疫吸血、领域、夹击效果
1. 在getItemEffect中修改拿到神圣盾时的效果标记一个自定义Flag。
2. 免疫吸血效果:在`enemys.js`的getDamage函数中找到extra_damage并编辑成如果存在神圣盾标记额外伤害为0。
3. 免疫领域、夹击效果:在`events.js`中找到checkBlock函数并编辑成如果有神圣盾标记则直接返回。
4. 如果有更高的需求,例如想让吸血效果变成一半(如异空间),则还是在上面这些地方进行对应的修改即可。
## 自定义怪物属性
如果你对现有的怪物不满意,想自行添加怪物属性(例如让怪物拥有双属性乃至更多属性),也是可以的。具体参见`enemys.js`文件。
你需自己指定一个special数字修改getSpecialText函数。
如果要修改伤害计算公式请修改下面的calDamage函数。请注意如果无法战斗该函数必须返回`999999999`。
因此无敌属性可以这样设置:
``` js
if (hero_atk <= mon_def) return 999999999; // 不可战斗时请直接返回999999999
```
对于吸血怪的额外伤害计算在getDamage中的`extra_damage`中。
对于毒衰弱怪物的战斗后结算在`events.js`中的afterBattle函数中。
对于领域、夹击怪物的检查在`events.js`中的checkBlock函数中。
`getCritical`, `getCriticalDamage`和`getDefDamage`三个函数依次计算的是该怪物的临界值、临界减伤和1防减伤。也可以适当进行修改。
## 自定义地图
遗憾的是所有地图数据必须在剧本的map中指定换句话说我们无法在游戏进行中动态修改地图比如为简单难度增加一个血瓶。
幸运的是,我们可以采用如下方式进行难度分歧,为用户简单难度下增加额外的血瓶或宝石。
``` js
"firstArrive": [ // 第一次到该楼层触发的事件
{"type": "if", "condition": "flag:hard!=3", // 判断是否困难难度
"true": [ // 不为困难,则为普通或简单难度
{"type": "show", "loc": [3, 6]} // 显示血瓶
{"type": "if", "condition": "flag:hard==1", // 判断是否是简单难度
"true": [
{"type": "show", "loc": [3, 7]} // 简单难度则显示宝石
],
"false": [] // 普通难度则只显示血瓶
},
],
"false": [] // 困难难度,不进行任何操作
},
],
"events": {
"3, 6": {"enable": false} // 比如[3, 6]点是一个血瓶,初始不可见
"3, 7": {"enable": false} // 比如[3, 7]点是一个宝石,初始不可见
}
```
如上所示,我们在地图上设置一个额外的血瓶和宝石,并初始时设为禁用状态。
当第一次到达该楼层时,进行一次判断;如果不为困难难度,则将血瓶显示出来;再判断是否为简单难度,如果是则再把宝石显示出来。
通过对`flag:hard`进行判断的方式,我们也可以达成“对于不同的难度有着不同的地图效果”。

75
docs/serviceWorker.js Normal file
View File

@ -0,0 +1,75 @@
const RUNTIME = 'docsify'
const HOSTNAME_WHITELIST = [
self.location.hostname,
'fonts.gstatic.com',
'fonts.googleapis.com',
'unpkg.com'
]
// The Util Function to hack URLs of intercepted requests
const getFixedUrl = (req) => {
var now = Date.now()
var url = new URL(req.url)
// 1. fixed http URL
// Just keep syncing with location.protocol
// fetch(httpURL) belongs to active mixed content.
// And fetch(httpRequest) is not supported yet.
url.protocol = self.location.protocol
// 2. add query for caching-busting.
// Github Pages served with Cache-Control: max-age=600
// max-age on mutable content is error-prone, with SW life of bugs can even extend.
// Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
// Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
if (url.hostname === self.location.hostname) {
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
}
return url.href
}
/**
* @Lifecycle Activate
* New one activated when old isnt being used.
*
* waitUntil(): activating ====> activated
*/
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim())
})
/**
* @Functional Fetch
* All network requests are being intercepted here.
*
* void respondWith(Promise<Response> r)
*/
self.addEventListener('fetch', event => {
// Skip some of cross-origin requests, like those for Google Analytics.
if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
// Stale-while-revalidate
// similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
// Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
const cached = caches.match(event.request)
const fixedUrl = getFixedUrl(event.request)
const fetched = fetch(fixedUrl, { cache: 'no-store' })
const fetchedCopy = fetched.then(resp => resp.clone())
// Call respondWith() with whatever we get first.
// If the fetch fails (e.g disconnected), wait for the cache.
// If theres nothing in cache, wait for the fetch.
// If neither yields a response, return offline pages.
event.respondWith(
Promise.race([fetched.catch(_ => cached), cached])
.then(resp => resp || fetched)
.catch(_ => { /* eat any errors */ })
)
// Update the cache with the version we fetched (only for ok status)
event.waitUntil(
Promise.all([fetchedCopy, caches.open(RUNTIME)])
.then(([response, cache]) => response.ok && cache.put(event.request, response))
.catch(_ => { /* eat any errors */ })
)
}
})

119
docs/start.md Normal file
View File

@ -0,0 +1,119 @@
# 快速上手
在这一节中,将详细介绍做一部塔的流程。现在,让我们来做一部单层塔!
## 前置需求
你需要有满足如下条件才能进行制作:
- Windows 8以上操作系统Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“地图生成器.exe”即可
- 任一款现代浏览器。强烈推荐Chrome。
- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如WebStormVSCode或者至少也要Sublime Text。[Sublime Text下载地址](https://www.sublimetext.com/ ),如提示注册的话百度随便搜索一个注册码输入即可。
- RPG Maker XP任一个魔塔样板推荐魔塔样板7630
只要满足了上述条件,你就可以开始做自己的塔啦!
## 新建剧本
类似于RMXP本塔每层楼都是一个“剧本”剧本内主要定义了本层的地图和各种事件。主函数将读取每个剧本并生成实际的地图供游戏使用。
我们打开 `libs/floors/` 目录这个目录是所有剧本的目录。我们需要指定一个楼层名例如MT1然后我们可以将`MT0.js`(模板)复制重命名为为`MT1.js`,并使用文本编辑器打开。(参见下面的图)。
然后将楼层名改为MT1floorId改名为MT1title可以改成任意内容将在切换楼层时进行显示比如可以改成“1层小塔”
具体样板文件的每个要素都有详细的注释。我们最终的任务其实是,将每个楼层的剧本(地图&事件)给写完即可。
![新建剧本](./img/script.png)
换句话说,只需要简单的复制操作,我们就可以新建一个剧本了。
## 绘制地图
遗憾的是我们的样板是没有像RMXP那样有着很好的UI界面供大家直接进行绘图可视化操作的。然而我们仍然可以利用已有的RMXP和魔塔样板绘制好地图然后利用目录中的“地图生成器”来转成样板所识别的格式。
首先我们打开RMXP和魔塔样板来到绘制地图页面。
![绘制地图](./img/rmxp1.png)
然后,任意绘制一张地图。
在这里我们就以1层小塔的地图为例。你也可以任意绘制自己的地图
![绘制地图](./img/rmxp2.png)
(我把原塔素材改成了经典样式,但是本质上是一样的。)
请注意,我们无需编辑任何事件。只需要让地图“看起来长成这个样子”就行。
当绘制好需要的地图后我们打开Windows自带的“截图工具”并将整个地图有效区域截图下来并将其复制到剪切板。
![绘制地图](./img/rmxp3.png)
截图时请注意只截取有效游戏空间内数据并且有效空间内的范围必须是13\*13。如果地图小于13\*13请用星空或墙壁填充到13\*13
确认地图的图片文件已经复制到剪切板后我们打开“地图生成器”并点“加载图片”。大约1-2秒后可以得到地图的数据。
![生成地图](./img/map1.png)
如果有识别不一致的存在即生成的地图和实际的地图不符则需要在左边的输入框内实际手动修改然后再点“图片生成”即可。有关每个数字对应的图块名称请参见images目录下的`meaning.txt`
!> 注地图生成器默认只支持经典素材。如果有自定义素材需求例如原版的1层小塔那种素材请参见[自定义素材](./personalization#自定义素材)。
经过确认生成的地图和原始地图保持一致后点击“复制地图”然后粘贴到刚刚剧本文件里的maps中。
![地图数组](./img/mapArray.png)
通过这种在RMXP中画图截图复制再用地图生成器识别的方式我们成功将我们需要的地图变成了样板可识别的格式。
## 录入数据
有了地图后,我们下一步需要做的就是录入数据。数据主要包括如下几种:
- 勇士初始的属性
- 全局变量宝石效果、全局Flag等
- 怪物数据(每个怪物的攻防血金币经验等等)
下面依次进行说明。
我们打开`data.js`文件,这里面定义了各种全局属性和勇士初始值。
我们可以将本塔标题改名为“1层小塔”游戏的唯一标识符叫onefloor然后可以直接修改勇士的各项初始数据.
![初始数据](./img/dataInit.png)
!> 请注意勇士的初始位置一栏x为横坐标y为纵坐标x为从左到右第几列y为从上到下第几行均从0开始计算。
修改完初始化信息后,接下来我们需要修改道具的信息(比如宝石加攻防的数值,血瓶加生命的数值等)。还是在这个`data.js`文件往下拉找到values一项并进行相应的设置
![修改数据](./img/modData.png)
然后再设置一些系统Flag以进行游戏。继续将`data.js`往下拉我们注意到本塔是存在魔防的不存在经验因此我们可以简单地将enableMDef改为trueenableExperience改成false。
![系统标志](./img/flag1.png)
同理,本塔的破墙镐只能破面前的墙壁,因此`pickaxeFourDirections`需要改成`false`。
![系统标志](./img/flag2.png)
其他的几项暂时不会被涉及到,因此不用考虑。
全局变量修改完毕后,我们需要告诉主函数加载该楼层。打开`main.js`该文件和index.html同级找到`this.floorIds`项将其值改为楼层ID即MT1。
![修改楼层数据](./img/floordata.png)
最后一步就是录入怪物数据。打开`enemys.js`文件依次输入你在本塔内使用到的所有怪物的攻防血的数据。其中怪物的特殊属性special项与该文件下面的getSpecialText对应。
![怪物数据](./img/enemyarray.png)
只需要修改自己用到的怪物属性即可,其他没有用到的怪物完全无所谓。
做完后保存所有文件然后右键选择使用chrome浏览器打开`index.html`,就能立刻看到自己的塔并开始游戏啦!是不是很简单呢!
![保存](./img/save.png)
## 压缩与发布
当你将上述步骤完成后,你实际上已经做出来了一个魔塔,并且可以发布给大家进行游戏了。
目前而言发布有如下几种方式:
- 离线版本:直接将游戏文件夹打包,放到百度网盘等地方供用户下载;用户下载到本地后直接使用浏览器打开`index.html`进行游戏。
- 手机端的部分浏览器如chrome也支持本地网页可以下载到手机然后直接打开进行游戏。
- 在线版本:将游戏放到某个服务器上,大家在线联网游戏。
离线版本的好处是:先全部下载后再游戏,无需考虑流量的问题,也可以支持高清音乐的播放。坏处是:没办法在多平台之间迁移(平台同步的锅),而浏览器打开本地文件有丢失存档的风险。
在线版本的好处是:随时随地可以玩,可以多平台接档,还可以在后台看到一些统计信息,了解大概有多少人进行了游戏(如果需要);坏处是需要一个服务器,且还要考虑到用户流量的问题。
在此我们只讨论在线版本。当你决定发布游戏时强烈建议先将JS代码进行压缩以节省可能的IO请求以及网络流量。直接打开同目录下的“JS代码压缩工具”进行压缩即可。
压缩完毕后,你还需要将`main.js`中的useCompress从false改为true这样方才只会加载压缩后的文件。
如果你需要发布到服务器上且你没有服务器,可以直接将本塔发给我(`艾之葵`),我会给负责你部署上去的。我的服务器至少能保证两年内有效。
然后就是发布帖子、链接二维码,能让任何人在任何时候任何平台上都能进行游戏啦!是不是很简单呢!
在下面的几章里,将对样板的各个元件、事件等依次进行介绍。