diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..20d693c8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,12 @@ +# HTML5 魔塔样板说明文档 + +众所周知,魔塔的趋势是向移动端发展,贴吧中也常常能见到“求手机魔塔”的帖子。然而现有的工具中,NekoRPG有着比较大的局限性,游戏感较差,更是完全没法在iOS上运行。而一些APP的魔塔虽然可用,但是必须要下载安装,对于Android和iOS还必须开发不同的版本,非常麻烦。 + +但是,现在我们有了HTML5。 HTML5的画布(canvas)以及它被Android/iOS内置浏览器所支持的特性,可以让我们做出真正意义上的全平台覆盖的魔塔。 +事实上,在贴吧的试水发布也证明了,H5魔塔确实是可以成功的。两部即使是复刻的魔塔也受到了不少人的追捧,其流畅的手感和全平台支持的特性,也让很多没办法打开电脑的人爱不释手。 + +然而,一般而言使用非RMXP制作魔塔往往需要一定的编程技术,HTML5魔塔自然也不例外。但是,为了能让大家更加注重于“做塔”本身,而不用考虑做塔以外的各种脚本问题,我特意制作了这样一部HTML5的魔塔样板。 + +> 这个魔塔样板,可以让你在完全不懂任何编程的情况下,做出自己的H5魔塔。不会代码?没关系!只要你想做,就能做出来! + +下面,我将详细地介绍,如何使用这一个样板来制作属于自己的HTML5魔塔。 diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 00000000..039c2ea0 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,6 @@ + +- [快速上手](start) +- [元件说明](element) +- [事件](event) +- [个性化](personalization) +- [附录:API列表](api) diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..0d763ec2 --- /dev/null +++ b/docs/api.md @@ -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为目标楼层Id,stair可指定为上/下楼梯,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 //绘制“关于”界面 +``` diff --git a/docs/element.md b/docs/element.md new file mode 100644 index 00000000..03db5135 --- /dev/null +++ b/docs/element.md @@ -0,0 +1,90 @@ +# 元件说明 + +在本章中,将对样板里的各个元件进行说明。各个元件主要包括道具、门、怪物、楼梯等等。 + +请打开样板0层 `sample0.js` 进行参照对比。 + + +## 道具 + +本塔目前支持的所有道具列表在样板0层中已全部给出。当你在样板0层中拿到某个宝物时会有提示,这里不再赘述,详见拿到该道具的说明。 + +大多数宝物都有默认的效果,十字架和屠龙匕首暂未定义,如有自己的需求可参见[自定义道具效果](./personalization#自定义道具效果)。 + +!> 请注意,本塔没有"装备"的说法,所有剑盾拿到后将立刻作为攻防数值直接加到勇士的属性上。 + +拿到道具后将触发`afterGetItem`事件,有关事件的详细介绍请参见[事件](./event)。 + +如需修改某个道具的效果,在不同区域宝石数据发生变化等问题,请参见[自定义道具效果](./personalization#自定义道具效果)的说明。 + +## 门 + +本塔支持6种门,黄蓝红绿铁花。前五种门需要有对应的钥匙打开,花门只能通过调用`openDoor`事件进行打开。 + +本塔支持暗墙,但是暗墙也必须通过`openDoor`事件开启。例如,样板2层的小偷事件,就是可以打开一个暗墙的。 + +开门后可触发该层的`afterOpenDoor`事件,有关事件的详细介绍请参见第四章。 + +## 怪物 + +本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。 +如有自己的怪物素材需求请参见[自定义素材](./personalization#自定义素材)的内容。 + +怪物可以由特殊属性,每个怪物最多只能有一个特殊属性。怪物的特殊属性所对应的数字(special)在下面的`getSpecialText`中定义,请勿对已有的属性进行修改。 + + +怪物的伤害计算在下面的`calDamage`函数中,如有自己需求的伤害计算公式请修改该函数的代码。 + +如果`data.js`中的enableExperience为false,即不启用经验的话,怪物手册里将不显示怪物的经验值,打败怪物也不获得任何经验。 + +拿到幸运金币后,打怪获得的金币将翻倍。 + +吸血怪需要在怪物后添加value,代表吸血的比例。 + + +中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。 +衰弱怪让勇士衰弱后,攻防会暂时下降一定的数值(直到衰弱状态解除恢复);这个下降的数值同在`data.js`中的values定义。 + + +诅咒怪将让勇士陷入诅咒状态,诅咒状态下杀怪不获得金币和经验值。 + +领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 + + +出于游戏性能的考虑,我们不可能每走一步都对领域和夹击进行检查。因此我们需要在本楼层的 checkBlock 中指明哪些点可能会触发领域和夹击事件,在这些点才会对领域和夹击进行检查和处理。 + +!> 请注意这里的`"x,y"`代表该点的横坐标为x,纵坐标为y;即从左到右第x列,从上到下的第y行(从0开始计算)。 + +本塔不支持阻击、激光、仇恨、自爆、退化等属性。 + +(这些属性基本都太恶心了,恶心到都不想加上去) + +如有额外需求,可参见[自定义自定义怪物属性](./personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。 + +### 路障、楼梯、传送门 + +血网的伤害数值、中毒后每步伤害数值、衰弱时暂时攻防下降的数值,都在 `data.js` 的values内定义。 + +路障同样会尽量被自动寻路绕过。 + +有关楼梯和传送门,必须在该层样板的changeFloor里指定传送点的目标。 + + +!> 请注意这里的`"x,y"`代表该点的横坐标为x,纵坐标为y;即从左到右第x列,从上到下的第y行(从0开始计算)。如(6,0)代表最上面一行的正中间一列。 + +floorId指定的是目标楼层的唯一标识符(ID)。 + +后面可以写stair到upFloor或downFloor,表示将前往目标楼层的上楼梯/下楼梯位置。你也可以写loc然后指定目标点的坐标。 + +请注意的是,如果目标楼层有多个楼梯,写stair可能会导致到达的楼梯不确定,这时候请使用loc方式来指定具体的点位置。 + +可以指定direction为up/left/right/down,指定后勇士将面向该方向。 + +可以指定time,指定后切换动画时长为指定的数值。 + +楼梯和传送门默认可`"穿透"`。所谓穿透,就是当寻路穿过一个楼梯/传送门后,不会触发楼层传送事件,而是继续前进。通过系统Flag可以指定是否穿透,你也可以对每个传送点单独设置该项。 + + +上面就是整个样板中的各个元件说明。通过这种方式,你就已经可以做出一部没有任何事件的塔了。 + +尝试着做一个两到三层的塔吧! diff --git a/docs/event.md b/docs/event.md new file mode 100644 index 00000000..1b22bd7d --- /dev/null +++ b/docs/event.md @@ -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"`,则原事件会被覆盖。 + +这种情况下一般需采用后面的afterBattle,afterOpenDoor和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,使得显示效果满意后,再复制粘贴到你的剧情文本中。 + + +### 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为失败原因。 +你可以在这里修改失败界面时显示的文字。 diff --git a/docs/img/blood.png b/docs/img/blood.png new file mode 100644 index 00000000..7d369e74 Binary files /dev/null and b/docs/img/blood.png differ diff --git a/docs/img/changefloor.png b/docs/img/changefloor.png new file mode 100644 index 00000000..ef7170a5 Binary files /dev/null and b/docs/img/changefloor.png differ diff --git a/docs/img/dataInit.png b/docs/img/dataInit.png new file mode 100644 index 00000000..f0d4266e Binary files /dev/null and b/docs/img/dataInit.png differ diff --git a/docs/img/debuff.png b/docs/img/debuff.png new file mode 100644 index 00000000..a087cdd1 Binary files /dev/null and b/docs/img/debuff.png differ diff --git a/docs/img/domainEnemy.png b/docs/img/domainEnemy.png new file mode 100644 index 00000000..b74eabcb Binary files /dev/null and b/docs/img/domainEnemy.png differ diff --git a/docs/img/enemyArray.png b/docs/img/enemyArray.png new file mode 100644 index 00000000..d8022c57 Binary files /dev/null and b/docs/img/enemyArray.png differ diff --git a/docs/img/eventdebug.png b/docs/img/eventdebug.png new file mode 100644 index 00000000..0bf2ebe3 Binary files /dev/null and b/docs/img/eventdebug.png differ diff --git a/docs/img/flag1.png b/docs/img/flag1.png new file mode 100644 index 00000000..f7794aca Binary files /dev/null and b/docs/img/flag1.png differ diff --git a/docs/img/flag2.png b/docs/img/flag2.png new file mode 100644 index 00000000..9ed48e59 Binary files /dev/null and b/docs/img/flag2.png differ diff --git a/docs/img/floordata.png b/docs/img/floordata.png new file mode 100644 index 00000000..64db97de Binary files /dev/null and b/docs/img/floordata.png differ diff --git a/docs/img/floorset.png b/docs/img/floorset.png new file mode 100644 index 00000000..99c55ce8 Binary files /dev/null and b/docs/img/floorset.png differ diff --git a/docs/img/getSpecialText.png b/docs/img/getSpecialText.png new file mode 100644 index 00000000..4dd167b0 Binary files /dev/null and b/docs/img/getSpecialText.png differ diff --git a/docs/img/logo.png b/docs/img/logo.png new file mode 100644 index 00000000..389b77c8 Binary files /dev/null and b/docs/img/logo.png differ diff --git a/docs/img/map1.png b/docs/img/map1.png new file mode 100644 index 00000000..1f06abb5 Binary files /dev/null and b/docs/img/map1.png differ diff --git a/docs/img/mapArray.png b/docs/img/mapArray.png new file mode 100644 index 00000000..3c6cefef Binary files /dev/null and b/docs/img/mapArray.png differ diff --git a/docs/img/mapmean.png b/docs/img/mapmean.png new file mode 100644 index 00000000..b8213091 Binary files /dev/null and b/docs/img/mapmean.png differ diff --git a/docs/img/modData.png b/docs/img/modData.png new file mode 100644 index 00000000..dfa7b3bb Binary files /dev/null and b/docs/img/modData.png differ diff --git a/docs/img/rmxp1.png b/docs/img/rmxp1.png new file mode 100644 index 00000000..7dfee78f Binary files /dev/null and b/docs/img/rmxp1.png differ diff --git a/docs/img/rmxp2.png b/docs/img/rmxp2.png new file mode 100644 index 00000000..af62c402 Binary files /dev/null and b/docs/img/rmxp2.png differ diff --git a/docs/img/rmxp3.png b/docs/img/rmxp3.png new file mode 100644 index 00000000..137d6a9b Binary files /dev/null and b/docs/img/rmxp3.png differ diff --git a/docs/img/sample0.png b/docs/img/sample0.png new file mode 100644 index 00000000..a218190b Binary files /dev/null and b/docs/img/sample0.png differ diff --git a/docs/img/save.png b/docs/img/save.png new file mode 100644 index 00000000..b17b584a Binary files /dev/null and b/docs/img/save.png differ diff --git a/docs/img/script.png b/docs/img/script.png new file mode 100644 index 00000000..981b1964 Binary files /dev/null and b/docs/img/script.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..52205be8 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,45 @@ + + +
+ +