diff --git a/_docs/_sidebar.md b/_docs/_sidebar.md new file mode 100644 index 0000000..35d7f25 --- /dev/null +++ b/_docs/_sidebar.md @@ -0,0 +1,10 @@ + +- [快速上手](start) +- [元件说明](element) +- [事件编辑](event) +- [事件指令](instruction) +- [个性化](personalization) +- [脚本](script) +- [修改编辑器](editor) +- [UI编辑器](ui-editor) +- [附录:API列表](api) diff --git a/_docs/api.md b/_docs/api.md new file mode 100644 index 0000000..e9991ca --- /dev/null +++ b/_docs/api.md @@ -0,0 +1,2415 @@ +# 附录:API列表 + +?> 样板全部的API列表都在这里了! + +这里将列出所有被转发到core的API,没有被转发的函数此处不会列出,请自行在代码中查看。 + +本附录量较大,如有什么需求请自行Ctrl+F进行搜索。 + +如有任何疑问,请联系小艾寻求帮助。 + +## core.js + +core.js中只有很少的几个函数,主要是游戏开始前的初始化等。 + +但是,core中定义了很多游戏运行时的状态,这些状态很多都会被使用到。 + +```text +core.__SIZE__, core.__PIXELS__ +游戏窗口大小;对于13x13的游戏而言这两个值分别是13和416,15x15来说分别是15和480。 + + +core.material +游戏中的所有资源列表,具体分为如下内容: +core.material.animates (动画) +core.material.bgms (背景音乐) +core.material.enemys (怪物信息,来自于 project/enemys.js) +core.material.icons (图标信息,来自于 project/icons.js) +core.material.images (图片素材,存放了各项素材图片如items.png等) + core.material.images.autotile (所有的自动元件图片) + core.material.images.tilesets (所有的额外素材图片) + core.material.images.images (用户引入的其他图片) +core.material.items (道具信息) +core.material.sounds (音效) + + +core.animateFrame +主要是记录和requestAnimationFrame相关的一些数据,常用的如下: +core.animateFrame.totalTime (游戏总的运行时时间) +core.animateFrame.weather (当前的天气信息) +core.animateFrame.asyncId (当前的异步处理事件的内容) + + +core.musicStatus +主要是记录和音效相关的内容,常用的如下: +core.musicStatus.bgmStatus (音乐开启状态) +core.musicStatus.soundStatus (音效开启状态) +core.musicStatus.playingBgm (当前正在播放的BGM) +core.musicStatus.lastBgm (最近一次尝试播放的BGM) +core.musicStatus.volume (当前的音量) +core.musicStatus.cachedBgms (背景音乐的缓存内容) +core.musicStatus.cacheBgmCount (背景音乐的缓存数量,默认值是4) + + +core.platform +游戏平台相关信息,常见的几个如下: +core.platform.isPC (是否是电脑端) +core.platform.isAndroid (是否是安卓端) +core.platform.isIOS (是否是iOS端) + + +core.domStyle +游戏的界面信息,包含如下几个: +core.domStyle.scale (当前的放缩比) +core.domStyle.ratio (高清UI的放缩比) +core.domStyle.isVertical (当前是否是竖屏状态) +core.domStyle.showStatusBar (当前是否显示状态栏) +core.domStyle.toolbarBtn (当前是否显示工具栏) + + +core.bigmap +当前的地图的尺寸信息,主要包含如下几个 +core.bigmap.width (当前地图的宽度) +core.bigmap.height (当前地图的高度) +core.bigmap.offsetX (当前地图针对窗口左上角的偏移像素x) +core.bigmap.offsetY (当前地图针对窗口左上角的偏移像素y) +core.bigmap.tempCanvas (一个临时画布,可以用来临时绘制很多东西) + + +core.saves +和存档相关的信息,包含如下几个: +core.saves.saveIndex (上次保存或读取的存档编号) +core.saves.ids (当前存在存档的编号列表) +core.saves.autosave (自动存档的信息) +core.saves.favorite (收藏的存档) +core.saves.favoriteNames (自定义存档的名称) + + +core.status +游戏的状态相关,是整个游戏中最重要的东西,其核心是如下几条: +请注意,每次重新开始、存档或读档时,core.status都会重新初始化。 +core.status.played (当前是否在游戏中) +core.status.gameOver (当前是否已经游戏结束,即win或lose) +core.status.hero (勇士信息;此项和全塔属性中的hero大体是对应的) + core.status.hero.name 勇士名 + core.status.hero.lv 当前等级 + core.status.hero.hpmax 当前生命上限 + core.status.hero.hp 当前生命值 + core.status.hero.manamax 当前魔力上限 + core.status.hero.mana 当前魔力值 + core.status.hero.atk 当前攻击力 + core.status.hero.def 当前防御力 + core.status.hero.mdef 当前护盾值 + core.status.hero.money 当前金币值 + core.status.hero.exp 当前经验值 + core.status.hero.loc 当前的位置信息 + core.status.hero.equipment 当前装上的装备 + core.status.hero.items 当前拥有的道具信息 + core.status.hero.flags 当前的各项flag信息 + core.status.hero.step 当前的步数值 + core.status.hero.statistics 当前的统计信息 +core.status.floorId (当前所在的楼层) +core.status.maps (所有的地图信息) +core.status.thisMap (当前的地图信息,等价于core.status.maps[core.status.floorId]) +core.status.bgmaps (所有背景层的信息) +core.status.fgmaps (所有的前景层的信息) +core.status.checkBlock (地图上的阻激夹域信息,也作为光环的缓存) +core.status.lockControl (当前是否是控制锁定状态) +core.status.automaticRoute (当前的自动寻路信息) +core.status.route (当前记录的录像) +core.status.replay (录像回放时要用到的信息) +core.status.shops (所有全局商店信息) +core.status.textAttribute (当前的文字属性,如颜色、背景等信息,和setText事件对应) +core.status.globalAttribute (当前的全局属性,如边框色、装备栏等) +core.status.curtainColor (当前色调层的颜色) +core.status.globalAnimateObjs (当前的全局帧动画效果) +core.status.floorAnimateObjs (当前的楼层贴图帧动画效果) +core.status.boxAnimateObjs (当前的盒子帧动画效果,例如怪物手册中的怪物) +core.status.autotileAnimateObjs (当前楼层的自动元件动画效果) +core.status.globalAnimateStatus (当前的帧动画的状态) +core.status.animateObjs (当前的播放动画信息) + + +core.floorIds +一个数组,表示所有的楼层ID,和全塔属性中的floorIds一致。 + + +core.floors +从楼层文件中读取全部的地图数据。 +和core.status.maps不同的是,后者在每次重新开始和读档时都会重置,也允许被修改(会存入存档)。 +而core.floors全程唯一,不允许被修改。 + + +core.statusBar +状态栏信息,例如状态栏图片,图标,以及各个内容的DOM定义等。 +core.statusBar.images (所有的系统图标,和icons.png对应) +core.statusBar.icons (状态栏中绘制的图标内容) + + +core.values +所有的全局数值信息,和全塔属性中的values一致。 +此项允许被直接修改,会存入存档。 + + +core.flags +所有的全塔开关,和全塔属性中的flags一致。 +此项不允许被直接修改,如有需要请使用“设置系统开关”事件,或者调用core.setGlobalFlag这个API。 + + +core.plugin +定义的插件函数。 + + +core.doFunc(func, _this) +执行一个函数,func为函数体或者插件中的函数名,_this为使用的this。 +如果func为一个字符串,则视为插件中的函数名,同时_this将被设置成core.plugin。 +此函数剩余参数将作为参数被传入func。 +``` + +## actions.js + +主要是处理一些和用户交互相关的内容。 + +```text +doRegisteredAction: fn(action: string, params: ?) +执行一个用户交互行为 + +keyDown: fn(keyCode: number) +根据按下键的code来执行一系列操作 + +keyDownCtrl: fn() -> bool +长按Ctrl键时 + +keyUp: fn(keyCode: number, altKey?: bool, fromReplay?: bool) +根据放开键的code来执行一系列操作 + +longClick: fn(x: number, y: number, px: number, py: number, fromEvent?: bool) +长按 + +onStatusBarClick: fn(e?: Event) +点击自绘状态栏时 + +onclick: fn(x: number, y: number, px: number, py: number, stepPostfix?: [?]) +具体点击屏幕上(x,y)点时,执行的操作 + +ondown: fn(loc: {x: number, y: number, size: number}) +点击(触摸)事件按下时 + +onkeyDown: fn(e: Event) +按下某个键时 + +onkeyUp: fn(e: Event) +放开某个键时 + +onmousewheel: fn(direct: number) +滑动鼠标滚轮时的操作 + +onmove: fn(loc: {x: number, y: number, size: number}) +当在触摸屏上滑动时 + +onup: fn(loc: {x: number, y: number, size: number}) +当点击(触摸)事件放开时 + +pressKey: fn(keyCode: number) +按住某个键时 + +registerAction: fn(action: string, name: string, func: string|fn(params: ?), priority?: number) +此函数将注册一个用户交互行为。 +action: 要注册的交互类型,如 ondown, onclick, keyDown 等等。 +name: 你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。 +func: 执行函数。 +如果func返回true,则不会再继续执行其他的交互函数;否则会继续执行其他的交互函数。 +priority: 优先级;优先级高的将会被执行。此项可不填,默认为0 + +unregisterAction: fn(action: string, name: string) +注销一个用户交互行为 +``` + +## control.js + +负责整个游戏的核心控制系统,分为如下几个部分: +- requestAnimationFrame相关 +- 标题界面,开始和重新开始游戏 +- 自动寻路和人物行走相关 +- 画布、位置、阻激夹域、显伤等相关 +- 录像的回放相关 +- 存读档,自动存档,同步存档等相关 +- 人物属性和状态、位置、变量等相关 +- 天气、色调、音乐和音效的播放 +- 状态栏和工具栏相关 +- 界面resize相关 + +```text +addBuff: fn(name: string, value: number) +增减主角某个属性的百分比修正倍率,加减法叠加和抵消。等价于 core.setBuff(name, core.getBuff(name) + value) +例如:core.addBuff('atk', -0.1); // 主角获得一层“攻击力减一成”的负面效果 +name: 属性的英文名,请注意只能用于数值类属性哦,否则随后的乘法会得到NaN +value: 倍率的增量 + +addFlag: fn(name: string, value: number) +增减一个flag变量,等价于 core.setFlag(name, core.getFlag(name, 0) + value) +例如:core.addFlag('hatred', 1); // 增加1点仇恨值 +name: 变量名,支持中文 +value: 变量的增量 + +addGameCanvasTranslate: fn(x?: number, y?: number) +加减画布偏移 + +addStatus: fn(name: string, value: number) +增减主角的某个属性,等价于core.setStatus(name, core.getStatus(name) + value) +例如:core.addStatus('atk', 100'); // 给主角攻击力加100 +name: 属性的英文名 +value: 属性的增量 + +addSwitch: fn(x: number, y: number, floorId: string, name: string, value: number) +增加某个独立开关的值 + +autosave: fn(removeLast?: bool) +自动存档 + +checkAutosave: fn() +实际将自动存档写入存储 + +checkBgm: fn() +检查bgm状态 + +checkBlock: fn() +检查并执行领域、夹击、阻击事件 + +checkRouteFolding: fn() +检查录像折叠信息 + +chooseReplayFile: fn() +选择录像文件 + +clearAutomaticRouteNode: fn(x?: number, y?: number) +清除自动寻路路线 + +clearContinueAutomaticRoute: fn(callback?: fn()) +清空剩下的自动寻路列表 + +clearRouteFolding: fn() +清空录像折叠信息 + +clearStatus: fn() +清除游戏状态和数据 + +clearStatusBar: fn() +清空状态栏 + +continueAutomaticRoute: fn() +继续剩下的自动寻路操作 + +debug: fn() +开启调试模式, 此模式下可以按Ctrl键进行穿墙, 并忽略一切事件。 +此模式下不可回放录像和上传成绩。 + +doSL: fn(id?: string, type?: string) +实际进行存读档事件 + +drawDamage: fn(string|CanvasRenderingContext2D) +仅绘制地图显伤 + +drawHero: fn(status?: string, offset?: number, frame?: number) +绘制主角和跟随者并重置视野到以主角为中心 +例如:core.drawHero(); // 原地绘制主角的静止帧并重置视野野 +status: 只能为 stop, leftFoot 和 rightFoot,不填用stop。 +offset: 相对主角逻辑位置的偏移量,不填视为无偏移。 +frame: 绘制的第几帧 + +gatherFollowers: fn() +立刻聚集所有的跟随者 + +getAllSaves: fn(callback?: fn()) +获得所有存档内容 + +getBuff: fn(name: string) -> number +读取主角某个属性的百分比修正倍率,初始值为1 +例如:core.getBuff('atk'); // 主角当前能发挥出多大比例的攻击力 +name: 属性的英文名 + +getFlag: fn(name: string, defaultValue?: ?) +读取一个flag变量 +name: 变量名,支持中文 +defaultValue: 当变量不存在时的返回值,可选(事件流中默认填0)。 + +getHeroLoc: fn(name: string) -> string|number +读取主角的位置和/或朝向 +例如:core.getHeroLoc(); // 读取主角的位置和朝向 +name: 要读取横坐标还是纵坐标还是朝向还是都读取 +返回值:name ? core.status.hero.loc[name] : core.status.hero.loc + +getLvName: fn(lv?: number) -> string|number +根据级别的数字获取对应的名称,后者定义在全塔属性 +例如:core.getLvName(); // 获取主角当前级别的名称,如“下级佣兵” +lv: 级别的数字,不填则视为主角当前的级别 +返回值:级别的名称,如果不存在就还是返回数字 + +getMappedName: fn(name: string) -> string +获得映射文件名 + +getNakedStatus: fn(name: string) +获得勇士原始属性(无装备和衰弱影响) + +getNextLvUpNeed: fn() -> number +获得下次升级需要的经验值。 +升级扣除模式下会返回经验差值;非扣除模式下会返回总共需要的经验值。 +如果无法进行下次升级,返回null。 + +getPlayingSounds: fn(name?: string) -> [number] +获得当前正在播放的所有(指定)音效的id列表 +name: 音效名,可用别名;不填代表返回正在播放的全部音效 +返回值: 一个列表,每一项为一个正在播放的音效id;可用core.stopSound立刻停止播放 + +getRealStatus: fn(name: string) +计算主角的某个属性,包括百分比修正 +例如:core.getRealStatus('atk'); // 计算主角的攻击力,包括百分比修正。战斗使用的就是这个值 +name: 属性的英文名,请注意只能用于数值类属性哦,否则乘法会得到NaN + +getRealStatusOrDefault: fn(status?: ?, name?: string) +从status中获得实际属性(增幅后的),如果不存在则从勇士属性中获取 + +getSave: fn(index?: number, callback?: fn(data: ?)) +获得某个存档内容 + +getSaveIndexes: fn(callback?: fn()) +获得所有存在存档的存档位 + +getSaves: fn(ids?: ?, callback?: fn()) +获得某些存档内容 + +getStatus: fn(name: string) -> number +读取主角的某个属性,不包括百分比修正 +例如:core.getStatus('atk'); // 读取主角的攻击力 +name: 属性的英文名,其中'x'、'y'和'direction'会被特殊处理为 core.getHeroLoc(name),其他的会直接读取 core.status.hero[name] + +getStatusLabel: fn(name: string) -> string +获得某个状态的名字,如atk->攻击,def->防御等 + +getStatusOrDefault: fn(status?: ?, name?: string) +从status中获得属性,如果不存在则从勇士属性中获取 + +getSwitch: fn(x: number, y: number, floorId: string, name: string, defaultValue?: ?) +获得某个独立开关的值 + +hasFlag: fn(name: string) -> bool +判定一个flag变量是否存在且不为false、0、''、null、undefined和NaN +例如:core.hasFlag('poison'); // 判断主角当前是否中毒 +name: 变量名,支持中文 +此函数等价于 !!core.getFlag(name) + +hasSave: fn(index?: number) -> bool +判断某个存档位是否存在存档 + +hasSwitch: fn(x: number, y: number, floorId: string, name: string) -> bool +判定某个独立开关的值 + +hideStartAnimate: fn(callback?: fn()) +淡出标题画面 +例如:core.hideStartAnimate(core.startGame); // 淡出标题画面并开始新游戏,跳过难度选择 +callback: 标题画面完全淡出后的回调函数 + +hideStatusBar: fn(showToolbox?: bool) +隐藏状态栏 +showToolbox: 是否不隐藏竖屏工具栏 + +isMoving: fn() -> bool +当前是否正在移动 + +isPlaying: fn() -> bool +游戏是否已经开始 + +isReplaying: fn() -> bool +是否正在播放录像 + +loadData: fn(data?: ?, callback?: fn()) +从本地读档 + +lockControl: fn() +锁定用户控制,常常用于事件处理 + +moveAction: fn(callback?: fn()) +尝试前进一步,如果面前不可被踏入就会直接触发该点事件 +请勿直接使用此函数,如有需要请使用「勇士前进一步或撞击」事件 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +moveDirectly: fn(destX?: number, destY?: number, ignoreSteps?: number) +瞬间移动 + +moveHero: fn(direction?: string, callback?: fn()) +连续前进,不撞南墙不回头 +例如:core.moveHero(); // 连续前进 +direction: 可选,如果设置了就会先转身到该方向 +callback: 可选,如果设置了就只走一步 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +moveOneStep: fn(callback?: fn()) +每移动一格后执行的事件 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +moveViewport: fn(x: number, y: number, time?: number, callback?: fn()) +移动视野范围 + +nearHero: fn(x: number, y: number, n?: number) -> bool +判定主角是否身处某个点的锯齿领域(取曼哈顿距离) +例如:core.nearHero(6, 6, 6); // 判定主角是否身处点(6,6)的半径为6的锯齿领域 +x: 领域的中心横坐标 +y: 领域的中心纵坐标 +n: 领域的半径,不填视为1 + +nextX: fn(n?: number) -> number +获取主角面前第n格的横坐标 +例如:core.closeDoor(core.nextX(), core.nextY(), 'yellowDoor', core.turnHero); // 在主角面前关上一扇黄门,然后主角顺时针旋转90° +n: 目标格与主角的距离,面前为正数,背后为负数,脚下为0,不填视为1 + +nextY: fn(n?: number) -> number +获取主角面前第n格的纵坐标 +例如:core.jumpHero(core.nextX(2), core.nextY(2)); // 主角向前跃过一格,即跳跃靴道具的使用效果 +n: 目标格与主角的距离,面前为正数,背后为负数,脚下为0,不填视为1 + +pauseBgm: fn() +暂停背景音乐的播放 + +pauseReplay: fn() +暂停播放 + +playBgm: fn(bgm: string, startTime?: number) +播放背景音乐,中途开播但不计入存档且只会持续到下次场景切换。如需长期生效请将背景音乐的文件名赋值给flags.__bgm__ +例如:core.playBgm('bgm.mp3', 30); // 播放bgm.mp3,并跳过前半分钟 +bgm: 背景音乐的文件名,支持全塔属性中映射前的中文名 +startTime: 跳过前多少秒,不填则不跳过 + +playSound: fn(sound: string, pitch?: number, callback?: fn()) -> number +播放一个音效 +sound: 音效名;可以使用文件别名。 +pitch: 播放的音调;可选,如果设置则为30-300之间的数值;100为正常音调。 +callback: 可选,播放完毕后执行的回调函数。 +返回:一个数字,可用于core.stopSound的参数来只停止该音效。 + +registerAnimationFrame: fn(name: string, needPlaying: bool, func?: fn(timestamp: number)) +注册一个 animationFrame +name: 名称,可用来作为注销使用 +needPlaying: 是否只在游戏运行时才执行(在标题界面不执行) +func: 要执行的函数,或插件中的函数名;可接受timestamp(从页面加载完毕到当前所经过的时间)作为参数 + +registerReplayAction: fn(name: string, func: fn(action?: string) -> bool) +注册一个录像行为 +name: 自定义名称,可用于注销使用 +func: 具体执行录像的函数,可为一个函数或插件中的函数名; +需要接受一个action参数,代表录像回放时的下一个操作 +func返回true代表成功处理了此录像行为,false代表没有处理此录像行为。 + +registerResize: fn(name: string, func: fn(obj: ?)) +注册一个resize函数 +name: 名称,可供注销使用 +func: 可以是一个函数,或者是插件中的函数名;可以接受obj参数,详见resize函数。 + +registerWeather: fn(name: string, initFunc: fn(level: number), frameFunc?: fn(timestamp: number, level: number)) +注册一个天气 +name: 要注册的天气名 +initFunc: 当切换到此天气时的初始化;接受level(天气等级)为参数;可用于创建多个节点(如初始化雪花) +frameFunc: 每帧的天气效果变化;可接受timestamp(从页面加载完毕到当前所经过的时间)和level(天气等级)作为参数 +天气应当仅在weather层进行绘制,推荐使用core.animateFrame.weather.nodes用于节点信息。 + +removeFlag: fn(name: string) +删除某个flag/变量 + +removeSave: fn(index?: number, callback?: fn()) +删除某个存档 + +removeSwitch: fn(x: number, y: number, floorId: string, name: string) +删除某个独立开关 + +replay: fn() +回放下一个操作 + +resize: fn() +屏幕分辨率改变后重新自适应 + +resumeBgm: fn(resumeTime?: number) +恢复背景音乐的播放 +resumeTime: 从哪一秒开始恢复播放 + +resumeReplay: fn() +恢复播放 + +rewindReplay: fn() +回退到上一个录像节点 + +saveAndStopAutomaticRoute: fn() +保存剩下的寻路,并停止 + +saveData: fn() +存档到本地 + +screenFlash: fn(color: [number], time: number, times?: number, moveMode?: string, callback?: fn()) +画面闪烁 +例如:core.screenFlash([255, 0, 0, 1], 3); // 红屏一闪而过 +color: 一行三列(第四列视为1)或一行四列(第四列若大于1则会被视为1,第四列若填负数则会被视为0)的颜色数组,必填 +time: 单次闪烁时长,实际闪烁效果为先花其三分之一的时间渐变到目标色调,再花剩余三分之二的时间渐变回去 +times: 闪烁的总次数,不填或填0都视为1 +moveMode: 渐变方式 +callback: 闪烁全部完毕后的回调函数,可选 + +setAutoHeroMove: fn(steps: [?]) +连续行走 +例如:core.setAutoHeroMove([{direction: "up", step: 1}, {direction: "left", step: 3}]); // 上左左左 +steps: 压缩的步伐数组,每项表示朝某方向走多少步 + +setAutomaticRoute: fn(destX: number, destY: number, stepPostfix: [{x: number, y: number, direction: string}]) +半自动寻路,用于鼠标或手指拖动 +例如:core.setAutomaticRoute(0, 0, [{direction: "right", x: 4, y: 9}, {direction: "right", x: 5, y: 9}]); +destX: 鼠标或手指的起拖点横坐标 +destY: 鼠标或手指的起拖点纵坐标 +stepPostfix: 拖动轨迹的数组表示,每项为一步的方向和目标点。 + +setBgmSpeed: fn(speed: number, usePitch?: bool) +设置背景音乐的播放速度和音调 +speed: 播放速度,必须为30-300中间的值。100为正常速度。 +usePitch: 是否同时改变音调(部分设备可能不支持) + +setBuff: fn(name: string, value: number) +设置主角某个属性的百分比修正倍率,初始值为1, +倍率存放在flag: '__'+name+'_buff__' 中 +例如:core.setBuff('atk', 0.5); // 主角能发挥出的攻击力减半 +name: 属性的英文名,请注意只能用于数值类属性哦,否则随后的乘法会得到NaN +value: 新的百分比修正倍率,不填(效果上)视为1 + +setCurtain: fn(color?: [number], time?: number, moveMode?: string, callback?: fn()) +更改画面色调,不计入存档。如需长期生效请使用core.events._action_setCurtain()函数 +例如:core.setCurtain(); // 恢复画面色调,用时四分之三秒 +color: 一行三列(第四列视为1)或一行四列(第四列若大于1则会被视为1,第四列若为负数则会被视为0)的颜色数组,不填视为[0, 0, 0, 0] +time: 渐变时间,单位为毫秒。不填视为750ms,负数视为0(无渐变,立即更改) +moveMode: 渐变方式 +callback: 更改完毕后的回调函数,可选。事件流中常取core.doAction + +setDisplayScale: fn(delta: number) +设置屏幕放缩 + +setFlag: fn(name: string, value: ?) +设置一个flag变量 +例如:core.setFlag('poison', true); // 令主角中毒 +name: 变量名,支持中文 +value: 变量的新值,不填或填null视为删除 + +setGameCanvasTranslate: fn(ctx: string|CanvasRenderingContext2D, x: number, y: number) +设置大地图的偏移量 + +setHeroLoc: fn(name: string, value: string|number, noGather?: bool) +设置勇士位置 +值得注意的是,这句话虽然会使勇士改变位置,但并不会使界面重新绘制; +如需立刻重新绘制地图还需调用:core.clearMap('hero'); core.drawHero(); 来对界面进行更新。 +例如:core.setHeroLoc('x', 5) // 将勇士当前位置的横坐标设置为5。 +name: 要设置的坐标属性 +value: 新值 +noGather: 是否聚集跟随者 + +setHeroMoveInterval: fn(callback?: fn()) +设置行走的效果动画 + +setHeroOpacity: fn(opacity?: number, moveMode?: string, time?: number, callback?: fn()) +改变勇士的不透明度 + +setMusicBtn: fn() +设置音乐图标的显隐状态 + +setReplaySpeed: fn(speed: number) +设置播放速度 + +setStatus: fn(name: string, value: number) +设置主角的某个属性 +例如:core.setStatus('atk', 100); // 设置攻击力为100 +name: 属性的英文名,其中'x'、'y'和'direction'会被特殊处理为 core.setHeroLoc(name, value),其他的会直接对 core.status.hero[name] 赋值 +value: 属性的新值 + +setSwitch: fn(x: number, y: number, floorId: string, name: string, value?: ?) +设置某个独立开关的值 + +setToolbarButton: fn(useButton?: bool) +改变工具栏为按钮1-8 + +setViewport: fn(px?: number, py?: number) +设置视野范围 +px,py: 左上角相对大地图的像素坐标,不需要为32倍数 + +setWeather: fn(type?: string, level?: number) +设置天气,不计入存档。如需长期生效请使用core.events._action_setWeather()函数 +例如:core.setWeather('fog', 10); // 设置十级大雾天 +type: 新天气的类型,不填视为晴天 +level: 新天气(晴天除外)的级别,必须为不大于10的正整数,不填视为5 + +showStartAnimate: fn(noAnimate?: bool, callback?: fn()) +进入标题画面 +例如:core.showStartAnimate(); // 重启游戏但不重置bgm +noAnimate: 可选,true表示不由黑屏淡入而是立即亮屏 +callback: 可选,完全亮屏后的回调函数 + +showStatusBar: fn() +显示状态栏 + +speedDownReplay: fn() +减速播放 + +speedUpReplay: fn() +加速播放 + +startReplay: fn(list: [string]) +开始播放录像 + +stepReplay: fn() +单步播放 + +stopAutomaticRoute: fn() +停止自动寻路操作 + +stopReplay: fn(force?: bool) +停止播放 + +stopSound: fn(id?: number) +停止播放音效。如果未指定id则停止所有音效,否则只停止指定的音效。 + +syncLoad: fn() +从服务器加载存档 + +syncSave: fn(type?: string) +同步存档到服务器 + +triggerBgm: fn() +开启或关闭背景音乐的播放 + +triggerDebuff: fn(action: string, type: string|[string]) +获得或移除毒衰咒效果 +action: 要获得还是移除,'get'为获得,'remove'为移除 +type: 获得或移除的内容(poison/weak/curse),可以为字符串或数组 + +triggerReplay: fn() +播放或暂停录像回放 + +tryMoveDirectly: fn(destX: number, destY: number) +尝试瞬移,如果该点有图块/事件/阻激夹域捕则会瞬移到它旁边再走一步(不可踏入的话当然还是触发该点事件),这一步的方向优先和瞬移前主角的朝向一致 +例如:core.tryMoveDirectly(6, 0); // 尝试瞬移到地图顶部的正中央,以样板0层为例,实际效果是瞬移到了上楼梯下面一格然后向上走一步并触发上楼事件 +destX: 目标点的横坐标 +destY: 目标点的纵坐标 + +turnHero: fn(direction?: string) +主角转向并计入录像,不会导致跟随者聚集,会导致视野重置到以主角为中心 +例如:core.turnHero(); // 主角顺时针旋转90°,即单击主角或按下Z键的效果 +direction: 主角的新朝向,可为 up, down, left, right, :left, :right, :back 七种之一 + +unlockControl: fn() +解锁用户控制行为 + +unregisterAnimationFrame: fn(name: string) +注销一个animationFrame + +unregisterReplayAction: fn(name: string) +注销一个录像行为 + +unregisterResize: fn(name: string) +注销一个resize函数 + +unregisterWeather: fn(name: string) +注销一个天气 + +updateCheckBlock: fn(floorId?: string) +更新领域、夹击、阻击的伤害地图 + +updateDamage: fn(floorId?: string, ctx?: string|CanvasRenderingContext2D) +重算并绘制地图显伤 +例如:core.updateDamage(); // 更新当前地图的显伤,绘制在显伤层(废话) +floorId: 地图id,不填视为当前地图。预览地图时填写 +ctx: 绘制到的画布,如果填写了就会画在该画布而不是显伤层 + +updateFollowers: fn() +更新跟随者坐标 + +updateHeroIcon: fn(name: string) +更新状态栏的勇士图标 + +updateStatusBar: fn(doNotCheckAutoEvents?: bool) +立刻刷新状态栏和地图显伤 +doNotCheckAutoEvents: 是否不检查自动事件 + +updateViewport: fn() +更新大地图的可见区域 + +waitHeroToStop: fn(callback?: fn()) +等待主角停下 +例如:core.waitHeroToStop(core.vibrate); // 等待主角停下,然后视野左右抖动1秒 +callback: 主角停止后的回调函数 +``` + +## enemys.js + +定义了一系列和怪物相关的API函数。 + +```text +canBattle: fn(enemy: string|enemy, x?: number, y?: number, floorId?: string) -> bool +判定主角当前能否打败某只敌人 +例如:core.canBattle('greenSlime',0,0,'MT0') // 能否打败主塔0层左上角的绿头怪(假设有) +enemy: 敌人id或敌人对象 +x: 敌人的横坐标,可选 +y: 敌人的纵坐标,可选 +floorId: 敌人所在的地图,可选 +返回值:true表示可以打败,false表示无法打败 + +getCurrentEnemys: fn(floorId?: string) -> [enemy] +获得某张地图的敌人集合,用于手册绘制 +例如:core.getCurrentEnemys('MT0') // 主塔0层的敌人集合 +floorId: 地图id,可选 +返回值:敌人集合,按伤害升序排列,支持多朝向怪合并 + +getDamage: fn(enemy: string|enemy, x?: number, y?: number, floorId?: string) -> number +获得某只敌人对主角的总伤害 +例如:core.getDamage('greenSlime',0,0,'MT0') // 绿头怪的总伤害 +enemy: 敌人id或敌人对象 +x: 敌人的横坐标,可选 +y: 敌人的纵坐标,可选 +floorId: 敌人所在的地图,可选 +返回值:总伤害,如果因为没有破防或无敌怪等其他原因无法战斗,则返回null + +getDamageInfo: fn(enemy: string|enemy, hero?: ?, x?: number, y?: number, floorId?: string) -> {damage: number, per_damage: number, hero_per_damage: number, init_damage: number, mon_hp: number, mon_atk: number, mon_def: number, turn: number} +获得战斗伤害信息 +例如:core.getDamage('greenSlime',0,0,'MT0') // 绿头怪的总伤害 +enemy: 敌人id或敌人对象 +hero: 可选,此时的勇士属性 +x: 敌人的横坐标,可选 +y: 敌人的纵坐标,可选 +floorId: 敌人所在的地图,可选 +返回值:伤害计算信息,如果因为没有破防或无敌怪等其他原因无法战斗,则返回null + +getDamageString: fn(enemy: string|enemy, x?: number, y?: number, floorId?: string) -> {color: string, damage: string} +获得某只敌人的地图显伤,包括颜色 +例如:core.getDamageString('greenSlime', 0, 0, 'MT0') // 绿头怪的地图显伤 +enemy: 敌人id或敌人对象 +x: 敌人的横坐标,可选 +y: 敌人的纵坐标,可选 +floorId: 敌人所在的地图,可选 +返回值:damage: 表示伤害值或为'???',color: 形如'#RrGgBb' + +getDefDamage: fn(enemy: string|enemy, k?: number, x?: number, y?: number, floorId?: string) -> number +计算再加若干点防御能使某只敌人对主角的总伤害降低多少 +例如:core.getDefDamage('greenSlime', 10, 0, 0, 'MT0') // 再加10点防御能使绿头怪的伤害降低多少 +enemy: 敌人id或敌人对象 +k: 假设主角增加的防御力,可选,默认为1 +x: 敌人的横坐标,可选 +y: 敌人的纵坐标,可选 +floorId: 敌人所在的地图,可选 + +getEnemyInfo: fn(enemy: string|enemy, hero?: ?, x?: number, y?: number, floorId?: string) -> {hp: number, atk: number, def: number, money: number, exp: number, special: [number], point: number, guards: [?]} +获得怪物真实属性 +hero: 可选,此时的勇士属性 +此函数将会计算包括坚固、模仿、光环等若干效果,将同时被怪物手册和伤害计算调用 + +getEnemys: fn() +获得所有怪物原始数据的一个副本。 +请使用core.material.enemys获得当前各项怪物属性。 + +getEnemyValue: fn(enemy?: string|enemy, name: string, x?: number, y?: number, floorId?: string) +获得某个点上怪物的某个属性值 + +getSpecialColor: fn(enemy: string|enemy) -> [string] +获得某个怪物所有特殊属性的颜色 + +getSpecialFlag: fn(enemy: string|enemy) -> number +获得某个怪物所有特殊属性的额外标记。 + +例如,1为全图性技能,需要进行遍历全图(光环/支援等) + +getSpecialHint: fn(enemy: string|enemy, special: number) -> string +获得某种敌人的某种特殊属性的介绍 +例如:core.getSpecialHint('bat', 1) // '先攻:怪物首先攻击' +enemy: 敌人id或敌人对象,用于确定属性的具体数值,否则可选 +special: 属性编号,可以是该敌人没有的属性 +返回值:属性的介绍,以属性名加中文冒号开头 + +getSpecialText: fn(enemy: string|enemy) -> [string] +获得某种敌人的全部特殊属性名称 +例如:core.getSpecialText('greenSlime') // ['先攻', '3连击', '破甲', '反击'] +enemy: 敌人id或敌人对象,如core.material.enemys.greenSlime +返回值:字符串数组 + +getSpecials: fn() -> [[?]] +获得所有特殊属性的定义 + +hasEnemyLeft: fn(enemyId?: string, floorId?: string|[string]) -> bool +检查某些楼层是否还有漏打的(某种)敌人 +例如:core.hasEnemyLeft('greenSlime', ['sample0', 'sample1']) // 样板0层和1层是否有漏打的绿头怪 +enemyId: 敌人id,可选,null表示任意敌人 +floorId: 地图id或其数组,可选,不填为当前地图 +返回值:地图中是否还存在该种敌人 + +hasSpecial: fn(special: number|[number]|string|number, test: number) -> bool +判定某种特殊属性的有无 +例如:core.hasSpecial('greenSlime', 1) // 判定绿头怪有无先攻属性 +special: 敌人id或敌人对象或正整数数组或自然数 +test: 待检查的属性编号 + + +nextCriticals: fn(enemy: string|enemy, number?: number, x?: number, y?: number, floorId?: string) -> [[number]] +获得某只敌人接下来的若干个临界及其减伤,算法基于useLoop开关选择回合法或二分法 +例如:core.nextCriticals('greenSlime', 9, 0, 0, 'MT0') // 绿头怪接下来的9个临界 +enemy: 敌人id或敌人对象 +number: 要计算的临界数量,可选,默认为1 +x: 敌人的横坐标,可选 +y: 敌人的纵坐标,可选 +floorId: 敌人所在的地图,可选 +返回:两列的二维数组,每行表示一个临界及其减伤 +``` + +## events.js + +events.js将处理所有和事件相关的操作,主要分为五个部分: +- 游戏的开始和结束 +- 系统事件的处理 +- 自定义事件的处理 +- 点击状态栏图标所进行的操作 +- 一些具体事件的执行内容 + +```text +afterBattle: fn(enemyId?: string, x?: number, y?: number) +战斗结束后触发的事件 + +afterChangeFloor: fn(floorId?: string) +转换楼层结束的事件 + +afterGetItem: fn(id?: string, x?: number, y?: number, isGentleClick?: bool) +获得一个道具后的事件 + +afterOpenDoor: fn(doorId?: string, x?: number, y?: number) +开一个门后触发的事件 + +afterPushBox: fn() +推箱子后的事件 + +autoEventExecuted: fn(symbol?: string, value?: ?) -> bool +当前是否执行过某个自动事件 + +autoEventExecuting: fn(symbol?: string, value?: ?) -> bool +当前是否在执行某个自动事件 + +battle: fn(id: string, x?: number, y?: number, force?: bool, callback?: fn()) +战斗,如果填写了坐标就会删除该点的敌人并触发战后事件 +例如:core.battle('greenSlime'); // 和从天而降的绿头怪战斗(如果打得过) +id: 敌人id,必填 +x: 敌人的横坐标,可选 +y: 敌人的纵坐标,可选 +force: true表示强制战斗,可选 +callback: 回调函数,可选 + +beforeBattle: fn(enemyId?: string, x?: number, y?: number) -> bool +战斗前触发的事件;返回false代表不进行战斗 + +changeFloor: fn(floorId: string, stair?: string, heroLoc?: {x?: number, y?: number, direction?: string}, time?: number, callback?: fn()) +场景切换 +例如:core.changeFloor('MT0'); // 传送到主塔0层,主角坐标和朝向不变,黑屏时间取用户定义的值 +floorId: 传送的目标地图id,可以填':before'和':next'分别表示楼下或楼上 +stair: 传送的位置 +heroLoc: 传送的坐标;会覆盖stair +time: 传送的黑屏时间,单位为毫秒;不填为用户设置值 +callback: 传送的回调函数 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +changingFloor: fn(floorId?: string, heroLoc?: {x: number, y: number, direction: string}) +楼层转换中 + +checkAutoEvents: fn() +检测自动事件 + +checkLvUp: fn() +检查升级事件 + +clearTextBox: fn(code: number) +清除对话框 + +closeDoor: fn(x: number, y: number, id: string, callback?: fn()) +关门,目标点必须为空地 +例如:core.closeDoor(0, 0, 'yellowWall', core.jumpHero); // 在左上角关掉一堵黄墙,然后主角原地跳跃半秒 +x: 横坐标 +y: 纵坐标 +id: 门的id,也可以用三种基础墙 +callback: 门完全关上后的回调函数,可选 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +confirmRestart: fn() +询问是否需要重新开始 + +doAction: fn() +执行下一个事件指令,常作为回调 +例如:core.setCurtain([0,0,0,1], undefined, null, core.doAction); // 事件中的原生脚本,配合勾选“不自动执行下一个事件”来达到此改变色调只持续到下次场景切换的效果 + +doEvent: fn(data?: ?, x?: number, y?: number, prefix?: string) +执行一个自定义事件 + +doSystemEvent: fn(type: string, data?: ?, callback?: fn()) +执行一个系统事件 + +eventMoveHero: fn(steps: [step], time?: number, callback?: fn()) +强制移动主角(包括后退),这个函数的作者已经看不懂这个函数了 +例如:core.eventMoveHero(['forward'], 125, core.jumpHero); // 主角强制前进一步,用时1/8秒,然后主角原地跳跃半秒 +steps: 步伐数组,注意后退时跟随者的行为会很难看 +time: 每步的用时,单位为毫秒。0或不填则取主角的移速,如果后者也不存在就取0.1秒 +callback: 移动完毕后的回调函数,可选 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +flyTo: fn(toId?: string, callback?: fn()) -> bool +飞往某一层 + +follow: fn(name: string) +跟随 +name: 要跟随的一个合法的4x4的行走图名称,需要在全塔属性注册 + +gameOver: fn(ending?: string, fromReplay?: bool, norank?: bool) +游戏结束 +例如:core.gameOver(); // 游戏失败 +ending: 结局名,省略表示失败 +fromReplay: true表示在播放录像,可选 +norank: true表示不计入榜单,可选 + +getCommonEvent: fn(name: string) -> [?] +获得一个公共事件 + +getItem: fn(id: string, num?: number, x?: number, y?: number, callback?: fn()) +获得道具并提示,如果填写了坐标就会删除该点的该道具 +例如:core.getItem('book'); // 获得敌人手册并提示 +id: 道具id,必填 +num: 获得的数量,不填视为1,填了就别填坐标了 +x: 道具的横坐标,可选 +y: 道具的纵坐标,可选 +callback: 回调函数,可选 + +getNextItem: fn(noRoute?: bool) +轻按获得面前的物品或周围唯一物品 +noRoute: 若为true则不计入录像 + +hasAsync: fn() -> bool +当前是否有未处理完毕的异步事件(不包含动画和音效) + +hasVisitedFloor: fn(floorId?: string) -> bool +是否到达过某个楼层 + +hideImage: fn(code: number, time?: number, callback?: fn()) +隐藏一张图片 +例如:core.hideImage(1, 1000, core.jumpHero); // 1秒内淡出1号图片,然后主角原地跳跃半秒 +code: 图片编号 +time: 淡出时间,单位为毫秒 +callback: 图片完全消失后的回调函数,可选 + +insertAction: fn(action: string|?|[?], x?: number, y?: number, callback?: fn(), addToLast?: bool) +插入一段事件;此项不可插入公共事件,请用 core.insertCommonEvent +例如:core.insertAction('一段文字'); // 插入一个显示文章 +action: 单个事件指令,或事件指令数组 +x: 新的当前点横坐标,可选 +y: 新的当前点纵坐标,可选 +callback: 新的回调函数,可选 +addToLast: 插入的位置,true表示插入到末尾,否则插入到开头 + +insertCommonEvent: fn(name?: string, args?: [?], x?: number, y?: number, callback?: fn(), addToLast?: bool) +插入一个公共事件 +例如:core.insertCommonEvent('加点事件', [3]); +name: 公共事件名;如果公共事件不存在则直接忽略 +args: 参数列表,为一个数组,将依次赋值给 flag:arg1, flag:arg2, ... +x: 新的当前点横坐标,可选 +y: 新的当前点纵坐标,可选 +callback: 新的回调函数,可选 +addToLast: 插入的位置,true表示插入到末尾,否则插入到开头 + +jumpHero: fn(ex?: number, ey?: number, time?: number, callback?: fn()) +主角跳跃,跳跃勇士。ex和ey为目标点的坐标,可以为null表示原地跳跃。time为总跳跃时间。 +例如:core.jumpHero(); // 主角原地跳跃半秒 +ex: 跳跃后的横坐标 +ey: 跳跃后的纵坐标 +time: 跳跃时长,单位为毫秒。不填视为半秒 +callback: 跳跃完毕后的回调函数,可选 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +load: fn(fromUserAction?: bool) +点击读档按钮时的打开操作 + +lose: fn(reason?: string) +游戏失败事件 + +moveEnemyOnPoint: fn(fromX: number, fromY: number, toX: number, toY: number, floorId?: string) +将某个点已经设置的敌人属性移动到其他点 + +moveImage: fn(code: number, to?: [number], opacityVal?: number, moveMode?: string, time?: number, callback?: fn()) +移动一张图片并/或改变其透明度 +例如:core.moveImage(1, null, 0.5); // 1秒内把1号图片变为50%透明 +code: 图片编号 +to: 新的左上角坐标,省略表示原地改变透明度 +opacityVal: 新的透明度,省略表示不变 +moveMode: 移动模式 +time: 移动用时,单位为毫秒。不填视为1秒 +callback: 图片移动完毕后的回调函数,可选 + +moveTextBox: fn(code: number, loc: [number], relative?: bool, moveMode?: string, time?: number, callback?: fn()) +移动对话框 + +onSki: fn(number?: number) -> bool +当前是否在冰上 + +openBook: fn(fromUserAction?: bool) +点击怪物手册时的打开操作 + +openDoor: fn(x: number, y: number, needKey?: bool, callback?: fn()) +开门(包括三种基础墙) +例如:core.openDoor(0, 0, true, core.jumpHero); // 打开左上角的门,需要钥匙,然后主角原地跳跃半秒 +x: 门的横坐标 +y: 门的纵坐标 +needKey: true表示需要钥匙,会导致机关门打不开 +callback: 门完全打开后或打不开时的回调函数,可选 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +openEquipbox: fn(fromUserAction?: bool) +点击装备栏时的打开操作 + +openKeyBoard: fn(fromUserAction?: bool) +点击虚拟键盘时的打开操作 + +openQuickShop: fn(fromUserAction?: bool) +点击快捷商店按钮时的打开操作 + +openSettings: fn(fromUserAction?: bool) +点击设置按钮时的操作 + +openToolbox: fn(fromUserAction?: bool) +点击工具栏时的打开操作 + +popEventLoc: fn() +将当前点坐标入栈 + +precompile: fn(data?: ?) +预编辑事件 + +pushBox: fn(data?: ?) +推箱子 + +pushEventLoc: fn(x?: number, y?: number, floorId?: string) -> bool +将当前点坐标入栈 + +recoverEvents: fn(data?: ?) +恢复一个事件 + +registerEvent: fn(type: string, func: fn(data: ?, x?: number, y?: number, prefix?: string)) +注册一个自定义事件 +type: 事件类型 +func: 事件的处理函数,可接受(data, x, y, prefix)参数 +data为事件内容,x和y为当前点坐标(可为null),prefix为当前点前缀 + +registerSystemEvent: fn(type: string, func: fn(data?: ?, callback?: fn())) +注册一个系统事件 +type: 事件名 +func: 为事件的处理函数,可接受(data,callback)参数 + +resetEnemyOnPoint: fn(x: number, y: number, floorId?: string) +重置某个点的怪物属性 + +resetGame: fn(hero?: ?, hard?: ?, floorId?: string, maps?: ?, values?: ?) +初始化游戏 + +restart: fn() +重新开始游戏;此函数将回到标题页面 + +rotateImage: fn(code: number, center?: [number], angle?: number, moveMode?: string, time?: number, callback?: fn()) +旋转一张图片 +code: 图片编号 +center: 旋转中心像素坐标(以屏幕为基准);不填视为图片本身中心 +angle: 旋转角度;正数为顺时针,负数为逆时针 +moveMode: 旋转模式 +time: 旋转用时,单位为毫秒。不填视为1秒 +callback: 图片旋转完毕后的回调函数,可选 + +save: fn(fromUserAction?: bool) +点击存档按钮时的打开操作 + +scaleImage: fn(code: number, center?: [number], scale?: number, moveMode?: string, time?: number, callback?: fn()) +放缩一张图片 + +setEnemy: fn(id: string, name: string, value: ?, operator?: string, prefix?: string) +设置一项敌人属性并计入存档 +例如:core.setEnemy('greenSlime', 'def', 0); // 把绿头怪的防御设为0 +id: 敌人id +name: 属性的英文缩写 +value: 属性的新值,可选 +operator: 运算操作符,可选 +prefix: 独立开关前缀,一般不需要,下同 + +setEnemyOnPoint: fn(x: number, y: number, floorId?: string, name: string, value: ?, operator?: string, prefix?: string) +设置某个点的敌人属性。如果该点不是怪物,则忽略此函数。 +例如:core.setEnemyOnPoint(3, 5, null, 'atk', 100, '+='); // 仅将(3,5)点怪物的攻击力加100。 + +setEvents: fn(list?: [?], x?: number, y?: number, callback?: fn()) +直接设置事件列表 + +setFloorInfo: fn(name: string, values: ?, floorId?: string, prefix?: string) +设置一项楼层属性并刷新状态栏 +例如:core.setFloorInfo('ratio', 2, 'MT0'); // 把主塔0层的血瓶和宝石变为双倍效果 +name: 要修改的属性名 +values: 属性的新值。 +floorId: 楼层id,不填视为当前层 +prefix: 独立开关前缀,一般不需要 + +setGlobalAttribute: fn(name: string, value: string) +设置全塔属性 + +setGlobalFlag: fn(name: string, value: bool) +设置一个系统开关 +例如:core.setGlobalFlag('steelDoorWithoutKey', true); // 使全塔的所有铁门都不再需要钥匙就能打开 +name: 系统开关的英文名 +value: 开关的新值,您可以用!core.flags[name]简单地表示将此开关反转 + +setHeroIcon: fn(name: string, noDraw?: bool) +更改主角行走图 +例如:core.setHeroIcon('npc48.png', true); // 把主角从阳光变成样板0层左下角的小姐姐,但不立即刷新 +name: 新的行走图文件名,可以是全塔属性中映射前的中文名。映射后会被存入core.status.hero.image +noDraw: true表示不立即刷新(刷新会导致大地图下视野重置到以主角为中心) + +setNameMap: fn(name: string, value?: string) +设置文件别名 + +setTextAttribute: fn(data: ?) +设置剧情文本的属性 + +setValue: fn(name: string, operator: string, value: ?, prefix?: string) +数值操作 + +setVolume: fn(value: number, time?: number, callback?: fn()) +调节bgm的音量 +例如:core.setVolume(0, 100, core.jumpHero); // 0.1秒内淡出bgm,然后主角原地跳跃半秒 +value: 新的音量,为0或不大于1的正数。注意系统设置中是这个值的平方根的十倍 +time: 渐变用时,单位为毫秒。不填或小于100毫秒都视为0 +callback: 渐变完成后的回调函数,可选 + +showGif: fn(name?: string, x?: number, y?: number) +绘制一张动图或擦除所有动图 +例如:core.showGif(); // 擦除所有动图 +name: 动图文件名,可以是全塔属性中映射前的中文名 +x: 动图在视野中的左上角横坐标 +y: 动图在视野中的左上角纵坐标 + +showImage: fn(code: number, image: string|image, sloc?: [number], loc?: [number], opacityVal?: number, time?: number, callback?: fn()) +显示一张图片 +例如:core.showImage(1, core.material.images.images['winskin.png'], [0,0,128,128], [0,0,416,416], 0.5, 1000); // 裁剪winskin.png的最左边128×128px,放大到铺满整个视野,1秒内淡入到50%透明,编号为1 +code: 图片编号,为不大于50的正整数,加上100后就是对应画布层的z值,较大的会遮罩较小的,注意色调层的z值为125,UI层为140 +image: 图片文件名(可以是全塔属性中映射前的中文名)或图片对象(见上面的例子) +sloc: 一行且至多四列的数组,表示从原图裁剪的左上角坐标和宽高,可选 +loc: 一行且至多四列的数组,表示图片在视野中的左上角坐标和宽高,可选 +opacityVal: 不透明度,为小于1的正数。不填视为1 +time: 淡入时间,单位为毫秒。不填视为0 +callback: 图片完全显示出来后的回调函数,可选 + +startEvents: fn(list?: [?], x?: number, y?: number, callback?: fn()) +开始执行一系列自定义事件 + +startGame: fn(hard: string, seed: number, route: string, callback?: fn()) +开始新游戏 +例如:core.startGame('咸鱼乱撞', 0, ''); // 开始一局咸鱼乱撞难度的新游戏,随机种子为0 +hard: 难度名,会显示在左下角(横屏)或右下角(竖屏) +seed: 随机种子,相同的种子保证了录像的可重复性 +route: 经由base64压缩后的录像,用于从头开始的录像回放 +callback: 回调函数,可选 + +stopAsync: fn() +立刻停止所有正在进行的异步事件 + +trigger: fn(x?: number, y?: number, callback?: fn()) +触发(x,y)点的系统事件;会执行该点图块的script属性,同时支持战斗(会触发战后)、道具(会触发道具后)、楼层切换等等 +callback: 执行完毕的回调函数 +【异步脚本,请勿在脚本中直接调用(而是使用对应的事件),否则可能导致录像出错】 + +tryUseItem: fn(itemId: string) +尝试使用一个道具 +例如:core.tryUseItem('pickaxe'); // 尝试使用破墙镐 +itemId: 道具id,其中敌人手册、传送器和飞行器会被特殊处理 + +unfollow: fn(name?: string) +取消跟随 +name: 取消跟随的行走图,不填则取消全部跟随者 + +unregisterEvent: fn(type: string) +注销一个自定义事件 + +unregisterSystemEvent: fn(type: string) +注销一个系统事件 + +useFly: fn(fromUserAction?: bool) +点击楼层传送器时的打开操作 + +vibrate: fn(direction?: string, time?: number, speed?: number, power?: number, callback?: fn()) +视野抖动 +例如:core.vibrate(); // 视野左右抖动1秒 +direction: 抖动方向;可填 horizontal(左右),vertical(上下),diagonal1(左上右下),diagonal2(左下右上) +time: 抖动时长,单位为毫秒 +speed: 抖动速度 +power: 抖动幅度 +callback: 抖动平息后的回调函数,可选 + +visitFloor: fn(floorId?: string) +到达某楼层 + +win: fn(reason?: string, norank?: bool, noexit?: bool) +游戏获胜事件 +``` + +## icons.js + +图标信息 + +```text +getAllIconIds: fn() -> [string] +获得所有图标的ID + +getClsFromId: fn(id?: string) -> string +根据ID获得其图标类型 + +getIcons: fn() +获得所有图标类型 + +getTilesetOffset: fn(id?: string) -> {image: ?, x: number, y: number} +根据图块数字或ID获得所在的tileset和坐标信息 +``` + +## items.js + +道具相关的函数 + +```text +addItem: fn(itemId: string, itemNum?: number) +静默增减某种道具的持有量 不会更新游戏画面或是显示提示 +例如:core.addItem('yellowKey', -2) // 没收两把黄钥匙 +itemId: 道具id +itemNum: 增加量,负数表示没收 + +canEquip: fn(equipId: string, hint?: bool) -> bool +检查能否穿上某件装备 +例如:core.canEquip('sword5', true) // 主角可以装备神圣剑吗,如果不能会有提示 +equipId: 装备id +hint: 无法穿上时是否提示(比如是因为未持有还是别的什么原因) +返回值:true表示可以穿上,false表示无法穿上 + +canUseItem: fn(itemId: string) -> bool +检查能否使用某种道具 +例如:core.canUseItem('pickaxe') // 能否使用破墙镐 +itemId: 道具id +返回值:true表示可以使用 + +compareEquipment: fn(compareEquipId: string, beComparedEquipId: string) -> {value: ?, percentage: ?} +比较两件(类型可不同)装备的优劣 +例如:core.compareEquipment('sword5', 'shield5') // 比较神圣剑和神圣盾的优劣 +compareEquipId: 装备甲的id +beComparedEquipId: 装备乙的id +返回值:两装备的各属性差,甲减乙,0省略 + +getEquip: fn(equipType: number) -> string +检查主角某种类型的装备目前是什么 +例如:core.getEquip(1) // 主角目前装备了什么盾牌 +equipType: 装备类型,自然数 +返回值:装备id,null表示未穿戴 + +getEquipTypeById: fn(equipId: string) -> number +判定某件装备的类型 +例如:core.getEquipTypeById('shield5') // 1(盾牌) +equipId: 装备id +返回值:类型编号,自然数 + +getEquipTypeByName: fn(name?: string) +根据类型获得一个可用的装备孔 + +getItemEffect: fn(itemId: string, itemNum?: number) +即捡即用类的道具获得时的效果 +例如:core.getItemEffect('redPotion', 10) // 执行获得10瓶红血的效果 +itemId: 道具id +itemNum: 道具数量,可选,默认为1 + +getItemEffectTip: fn(itemId: string) -> string +即捡即用类的道具获得时的额外提示 +例如:core.getItemEffectTip(redPotion) // (获得 红血瓶)',生命+100' +itemId: 道具id +返回值:图块属性itemEffectTip的内容 + +getItems: fn() +获得所有道具 + +hasEquip: fn(itemId: string) -> bool +检查主角是否穿戴着某件装备 +例如:core.hasEquip('sword5') // 主角是否装备了神圣剑 +itemId: 装备id +返回值:true表示已装备 + +hasItem: fn(itemId: string) -> bool +检查主角是否持有某种道具(不包括已穿戴的装备) +例如:core.hasItem('yellowKey') // 主角是否持有黄钥匙 +itemId: 道具id +返回值:true表示持有 + +itemCount: fn(itemId: string) -> number +统计某种道具的持有量 +例如:core.itemCount('yellowKey') // 持有多少把黄钥匙 +itemId: 道具id +返回值:该种道具的持有量,不包括已穿戴的装备 + +loadEquip: fn(equipId: string, callback?: fn()) +尝试穿上某件背包里面的装备并提示 +例如:core.loadEquip('sword5') // 尝试装备上背包里面的神圣剑,无回调 +equipId: 装备id +callback: 穿戴成功或失败后的回调函数 + +quickLoadEquip: fn(index: number) +快速换装 +例如:core.quickLoadEquip(1) // 快速换上1号套装 +index: 套装编号,自然数 + +quickSaveEquip: fn(index: number) +保存当前套装 +例如:core.quickSaveEquip(1) // 将当前套装保存为1号套装 +index: 套装编号,自然数 + +removeItem: fn(itemId?: string, itemNum?: number) +删除某个物品 + +setEquip: fn(equipId: string, valueType: string, name: string, value: ?, operator?: string, prefix?: string) +设置某个装备的属性并计入存档 +例如:core.setEquip('sword1', 'value', 'atk', 300, '+='); // 设置铁剑的攻击力数值再加300 +equipId: 装备id +valueType: 增幅类型,只能是value(数值)或percentage(百分比) +name: 要修改的属性名称,如atk +value: 要修改到的属性数值 +operator: 操作符,可选,如+=表示在原始值上增加 +prefix: 独立开关前缀,一般不需要 + +setItem: fn(itemId: string, itemNum?: number) +设置某种道具的持有量 +例如:core.setItem('yellowKey', 3) // 设置黄钥匙为3把 +itemId: 道具id +itemNum: 新的持有量,可选,自然数,默认为0 + +unloadEquip: fn(equipType: number, callback?: fn()) +脱下某个类型的装备 +例如:core.unloadEquip(1) // 卸下盾牌,无回调 +equipType: 装备类型编号,自然数 +callback: 卸下装备后的回调函数 + +useItem: fn(itemId: string, noRoute?: bool, callback?: fn()) +使用一个道具 +例如:core.useItem('pickaxe', true) // 使用破墙镐,不计入录像,无回调 +itemId: 道具id +noRoute: 是否不计入录像,快捷键使用的请填true,否则可省略 +callback: 道具使用完毕或使用失败后的回调函数 +``` + +## loader.js + +资源加载相关的函数 + +```text +freeBgm: fn(name: string) +释放一个bgm的缓存 + +loadBgm: fn(name: string) +加载一个bgm + +loadImage: fn(dir: name, imgName: name, callback?: fn()) +加载某一张图片 + +loadImages: fn(dir: string, names: [string], toSave: ?, callback?: fn()) +加载一系列图片 + +loadImagesFromZip: fn(url: string, names: [string], toSave?: ?, onprogress?: ?, onfinished?: ?) +从zip中加载一系列图片 + +loadOneMusic: fn(name: string) +加载一个音乐或音效 + +loadOneSound: fn(name: string) +加载一个音效 +``` + +## maps.js + +负责一切和地图相关的处理内容,包括如下几个方面: +- 地图的初始化,保存和读取,地图数组的生成 +- 是否可移动或瞬间移动的判定 +- 地图的绘制 +- 获得某个点的图块信息 +- 启用和禁用图块,改变图块 +- 移动/跳跃图块,淡入淡出图块 +- 全局动画控制,动画的绘制 + +```text +addGlobalAnimate: fn(block?: block) +添加一个全局动画 + +animateBlock: fn(loc?: [number]|[[number]], type?: string|number, time?: number, callback?: fn()) +显示/隐藏某个块时的动画效果 + +animateSetBlock: fn(number: number|string, x: number, y: number, floorId?: string, time?: number, callback?: fn()) +动画形式转变某点图块 + +animateSetBlocks: fn(number: number|string, locs: [?], floorId?: string, time?: number, callback?: fn()) +动画形式同时转变若干点图块 + +automaticRoute: fn(destX: number, destY: number) -> [{x: number, y: number, direction: string}] +自动寻路 +例如:core.automaticRoute(0, 0); // 自动寻路到地图左上角 +destX: 目标点的横坐标 +destY: 目标点的纵坐标 +返回值:每步走完后主角的loc属性组成的一维数组 + +canMoveDirectly: fn(destX: number, destY: number) -> number +能否瞬移到某点,并求出节约的步数。 +例如:core.canMoveDirectly(0, 0); // 能否瞬移到地图左上角 +destX: 目标点的横坐标 +destY: 目标点的纵坐标 +返回值:正数表示节约的步数,-1表示不可瞬移 + +canMoveDirectlyArray: fn(locs?: [[number]]) +获得某些点可否通行的信息 + +canMoveHero: fn(x?: number, y?: number, direction?: string, floorId?: string) -> bool +单点单朝向的可通行性判定;受各图层cannotInOut、起点cannotMove和canGoDeadZone影响,不受canPass和noPass影响 +x: 起点横坐标,不填视为主角当前的 +y: 起点纵坐标,不填视为主角当前的 +direction: 移动的方向,不填视为主角面对的方向 +floorId: 地图id,不填视为当前地图 + +compressMap: fn(mapArr: [[number]], floorId?: string) -> [[number]] +压缩地图 + +decompressMap: fn(mapArr: [[number]], floorId?: string) -> [[number]] +解压缩地图 + +drawAnimate: fn(name: string, x: number, y: number, alignWindow: bool, callback?: fn()) -> number +播放动画,注意即使指定了主角的坐标也不会跟随主角移动,如有需要请使用core.drawHeroAnimate(name, callback)函数 +例如:core.drawAnimate('attack', core.nextX(), core.nextY(), false, core.vibrate); // 在主角面前一格播放普攻动画,动画停止后视野左右抖动1秒 +name: 动画文件名,不含后缀 +x: 横坐标 +y: 纵坐标 +alignWindow: 是否是相对窗口的坐标 +callback: 动画停止后的回调函数,可选 +返回值:一个数字,可作为core.stopAnimate()的参数来立即停止播放(届时还可选择是否执行此次播放的回调函数) + +drawBg: fn(floorId?: string, ctx?: CanvasRenderingContext2D) +绘制背景层(含贴图,其与背景层矩阵的绘制顺序可通过复写此函数来改变) +例如:core.drawBg(); // 绘制当前地图的背景层 +floorId: 地图id,不填视为当前地图 +ctx: 某画布的ctx,用于绘制缩略图,一般不需要 + +drawBlock: fn(block?: block, animate?: number) +绘制一个图块 + +drawBoxAnimate: fn() +绘制UI层的box动画 + +drawEvents: fn(floorId?: string, blocks?: [block], ctx?: CanvasRenderingContext2D) +绘制事件层 +例如:core.drawEvents(); // 绘制当前地图的事件层 +floorId: 地图id,不填视为当前地图 +blocks: 一般不需要 +ctx: 某画布的ctx,用于绘制缩略图,一般不需要 + +drawFg: fn(floorId?: string, ctx?: CanvasRenderingContext2D) +绘制前景层(含贴图,其与前景层矩阵的绘制顺序可通过复写此函数来改变) +例如:core.drawFg(); // 绘制当前地图的前景层 +floorId: 地图id,不填视为当前地图 +ctx: 某画布的ctx,用于绘制缩略图,一般不需要 + +drawHeroAnimate: fn(name: string, callback?: fn()) -> number +播放跟随勇士的动画 +name: 动画名 +callback: 动画停止后的回调函数,可选 +返回值:一个数字,可作为core.stopAnimate()的参数来立即停止播放(届时还可选择是否执行此次播放的回调函数) + +drawMap: fn(floorId?: string) +地图重绘 +例如:core.drawMap(); // 重绘当前地图,常用于更改贴图或改变自动元件后的刷新 +floorId: 地图id,可省略表示当前楼层 +callback: 重绘完毕后的回调函数,可选 + +drawThumbnail: fn(floorId?: string, blocks?: [block], options?: ?) +绘制缩略图 +例如:core.drawThumbnail(); // 绘制当前地图的缩略图 +floorId: 地图id,不填视为当前地图 +blocks: 一般不需要 +options: 绘制信息,可选。可以增绘主角位置和朝向、采用不同于游戏中的主角行走图、增绘显伤、提供flags用于存读档,同时包含要绘制到的画布名或画布的ctx或还有其他信息,如起绘坐标、绘制大小、是否绘制全图、截取中心 + +enemyExists: fn(x: number, y: number, id?: string, floorId?: string) -> bool +某个点是否存在(指定的)怪物 + +extractBlocks: fn(map?: ?) +根据需求解析出blocks + +extractBlocksForUI: fn(map?: ?, flags?: ?) +根据需求为UI解析出blocks + +generateGroundPattern: fn(floorId?: string) +生成groundPattern + +generateMovableArray: fn(floorId?: string) -> [[[string]]] +可通行性判定 +例如:core.generateMovableArray(); // 判断当前地图主角从各点能向何方向移动 +floorId: 地图id,不填视为当前地图 +返回值:从各点可移动方向的三维数组 + +getBgMapArray: fn(floorId?: string, noCache?: bool) -> [[number]] +生成背景层矩阵 +例如:core.getBgMapArray('MT0'); // 生成主塔0层的背景层矩阵,使用缓存 +floorId: 地图id,不填视为当前地图 +noCache: 可选,true表示不使用缓存 +返回值:背景层矩阵,注意对其阵元的访问是[y][x] + +getBgNumber: fn(x?: number, y?: number, floorId?: string, noCache?: bool) -> number +判定某点的背景层的数字 +例如:core.getBgNumber(); // 判断主角脚下的背景层图块的数字 +x: 横坐标,不填为勇士坐标 +y: 纵坐标,不填为勇士坐标 +floorId: 地图id,不填视为当前地图 +noCache: 可选,true表示不使用缓存而强制重算 + +getBlock: fn(x: number, y: number, floorId?: string, showDisable?: bool) -> block +获得某个点的block + +getBlockById: fn(id: string) -> block +根据ID获得图块 + +getBlockByNumber: fn(number: number) -> block +根据数字获得图块 + +getBlockCls: fn(x: number, y: number, floorId?: string, showDisable?: bool) -> string +判定某个点的图块类型 +例如:if(core.getBlockCls(x1, y1) != 'enemys' && core.getBlockCls(x2, y2) != 'enemy48') core.openDoor(x3, y3); // 另一个简单的机关门事件,打败或炸掉这一对不同身高的敌人就开门 +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 +showDisable: 隐藏点是否不返回null,true表示不返回null +返回值:图块类型,即“地形、四帧动画、矮敌人、高敌人、道具、矮npc、高npc、自动元件、额外地形”之一 + +getBlockFilter: fn(x: number, y: number, floorId?: string, showDisable?: bool) -> ? +获得某个点的图块特效 + +getBlockId: fn(x: number, y: number, floorId?: string, showDisable?: bool) -> string +判定某个点的图块id +例如:if(core.getBlockId(x1, y1) != 'greenSlime' && core.getBlockId(x2, y2) != 'redSlime') core.openDoor(x3, y3); // 一个简单的机关门事件,打败或炸掉这一对绿头怪和红头怪就开门 +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 +showDisable: 隐藏点是否不返回null,true表示不返回null +返回值:图块id,该点无图块则返回null + +getBlockInfo: fn(block?: number|string|block) -> blockInfo +获得某个图块或素材的信息,包括ID,cls,图片,坐标,faceIds等等 + +getBlockNumber: fn(x: number, y: number, floorId?: string, showDisable?: bool) -> number +判定某个点的图块数字 +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 +showDisable: 隐藏点是否不返回null,true表示不返回null +返回值:图块数字,该点无图块则返回null + +getBlockOpacity: fn(x: number, y: number, floorId?: string, showDisable?: bool) -> number +判定某个点的不透明度。如果该点无图块则返回null。 + +getFaceDownId: fn(block?: string|number|block) -> string +获得某个图块对应行走图朝向向下的那一项的id;如果不存在行走图绑定则返回自身id。 + +getFgMapArray: fn(floorId?: string, noCache?: bool) -> [[number]] +生成前景层矩阵 +例如:core.getFgMapArray('MT0'); // 生成主塔0层的前景层矩阵,使用缓存 +floorId: 地图id,不填视为当前地图 +noCache: 可选,true表示不使用缓存 +返回值:前景层矩阵,注意对其阵元的访问是[y][x] + +getFgNumber: fn(x: number, y: number, floorId?: string, noCache?: bool) -> number +判定某点的前景层的数字 +例如:core.getFgNumber(); // 判断主角脚下的前景层图块的数字 +x: 横坐标,不填为勇士坐标 +y: 纵坐标,不填为勇士坐标floorId: 地图id,不填视为当前地图 +noCache: 可选,true表示不使用缓存而强制重算 + +getIdOfThis: fn(id?: string) -> string +获得当前事件点的ID + +getMapArray: fn(floorId?: string, noCache?: bool) -> [[number]] +生成事件层矩阵 +例如:core.getMapArray('MT0'); // 生成主塔0层的事件层矩阵,隐藏的图块视为0 +floorId: 地图id,不填视为当前地图 +showDisable: 可选,true表示隐藏的图块也会被表示出来 +返回值:事件层矩阵,注意对其阵元的访问是[y][x] + +getMapBlocksObj: fn(floorId?: string, noCache?: bool) +以x,y的形式返回每个点的事件 + +getMapNumber: fn(x: number, y: number, floorId?: string, noCache?: bool) -> number +获得事件层某个点的数字 + +getNumberById: fn(id: string) -> number +根据图块id得到数字(地图矩阵中的值) +例如:core.getNumberById('yellowWall'); // 1 +id: 图块id +返回值:图块的数字,定义在project\maps.js(请注意和project\icons.js中的“图块索引”相区分!) + +getPlayingAnimates: fn(name?: string) -> [number] +获得当前正在播放的所有(指定)动画的id列表 +name: 动画名;不填代表返回全部正在播放的动画 +返回值: 一个数组,每一项为一个正在播放的动画;可用core.stopAnimate停止播放。 + +hideBgFgMap: fn(name?: string, loc?: [number]|[[number]], floorId?: string, callback?: fn()) +隐藏前景/背景地图 + +hideBlock: fn(x: number, y: number, floorId?: string) +隐藏一个图块,对应于「隐藏事件」且不删除 +例如:core.hideBlock(0, 0); // 隐藏地图左上角的图块 +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 + +hideBlockByIndex: fn(index?: number, floorId?: string) +根据图块的索引来隐藏图块 + +hideBlockByIndexes: fn(indexes?: [number], floorId?: string) +一次性隐藏多个block + +hideFloorImage: fn(loc?: [number]|[[number]], floorId?: string, callback?: fn()) +隐藏一个楼层贴图 + +initBlock: fn(x: number, y: number, id: string|number, addInfo?: bool, eventFloor?: ?) -> block +初始化一个图块 + +isMapBlockDisabled: fn(floorId?: string, x?: number, y?: number, flags?: ?) -> bool +某个点图块是否被强制启用或禁用 + +jumpBlock: fn(sx: number, sy: number, ex: number, ey: number, time?: number, keep?: bool, callback?: fn()) +跳跃图块;从V2.7开始不再有音效 +例如:core.jumpBlock(0, 0, 0, 0); // 令地图左上角的图块原地跳跃半秒,再花半秒淡出 +sx: 起点的横坐标 +sy: 起点的纵坐标 +ex: 终点的横坐标 +ey: 终点的纵坐标 +time: 单步和淡出用时,单位为毫秒。不填视为半秒 +keep: 是否不淡出,true表示不淡出 +callback: 落地或淡出后的回调函数,可选 + +loadFloor: fn(floorId?: string, map?: ?) +从文件或存档中加载某个楼层 + +loadMap: fn(data?: ?, floorId?: string, flags?: ?) +将存档中的地图信息重新读取出来 + +moveBlock: fn(x: number, y: number, steps: [string], time?: number, keep?: bool, callback?: fn()) +移动图块 +例如:core.moveBlock(0, 0, ['down']); // 令地图左上角的图块下移一格 +x: 起点的横坐标 +y: 起点的纵坐标 +steps: 步伐数组 +time: 单步和淡出用时,单位为毫秒。不填视为半秒 +keep: 是否不淡出,true表示不淡出 +callback: 移动或淡出后的回调函数,可选 + +nearStair: fn() -> bool +当前位置是否在楼梯边;在楼传平面塔模式下对箭头也有效 + +noPass: fn(x: number, y: number, floorId?: string) -> bool +判定某个点是否不可被踏入(不基于主角生命值和图块cannotIn属性) +例如:core.noPass(0, 0); // 判断地图左上角能否被踏入 +x: 目标点的横坐标 +y: 目标点的纵坐标 +floorId: 目标点所在的地图id,不填视为当前地图 +返回值:true表示可踏入 + +npcExists: fn(x: number, y: number, floorId?: string) -> bool +某个点是否存在NPC + +removeBlock: fn(x: number, y: number, floorId?: string) +删除一个图块,对应于「隐藏事件」并同时删除 +例如:core.removeBlock(0, 0); // 尝试删除地图左上角的图块 +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 + +removeBlockByIndex: fn(index: number, floorId?: string) +根据block的索引删除该块 + +removeBlockByIndexes: fn(indexes?: [number], floorId?: string) +一次性删除多个block + +removeGlobalAnimate: fn(x?: number, y?: number, name?: string) +删除一个或所有全局动画 + +replaceBlock: fn(fromNumber: number, toNumber: number, floorId?: string|[string]) +批量替换图块 +例如:core.replaceBlock(21, 22, core.floorIds); // 把游戏中地上当前所有的黄钥匙都变成蓝钥匙 +fromNumber: 旧图块的数字 +toNumber: 新图块的数字 +floorId: 地图id或其数组,不填视为当前地图 + +resetMap: fn(floorId?: string|[string]) +重置地图 + +resizeMap: fn(floorId?: string) +更改地图画布的尺寸 + +saveMap: fn(floorId?: string) +将当前地图重新变成数字,以便于存档 + +searchBlock: fn(id: string, floorId?: string|[string], showDisable?: bool) -> [{floorId: string, index: number, x: number, y: number, block: block}] +搜索图块, 支持通配符和正则表达式 +例如:core.searchBlock('*Door'); // 搜索当前地图的所有门 +id: 图块id,支持星号表示任意多个(0个起)字符 +floorId: 地图id或数组,不填视为当前地图 +showDisable: 隐藏点是否计入,true表示计入 +返回值:一个详尽的数组,一般只用到其长度 + +searchBlockWithFilter: fn(blockFilter: fn(block: block) -> bool, floorId?: string|[string], showDisable?: bool): [{floorId: string, index: number, x: number, y: number, block: block}] +根据给定的筛选函数搜索全部满足条件的图块 +例如:core.searchBlockWithFilter(function (block) { return block.event.id.endsWith('Door'); }); // 搜索当前地图的所有门 +blockFilter: 筛选函数,可接受block输入,应当返回一个boolean值 +floorId: 地图id或数组,不填视为当前地图 +showDisable: 隐藏点是否计入,true表示计入 +返回值:一个详尽的数组 + +setBgFgBlock: fn(name: string, number: number|string, x: number, y: number, floorId?: string) +转变图层块 +例如:core.setBgFgBlock('bg', 167, 6, 6); // 把当前地图背景层的中心块改为滑冰 +name: 背景还是前景 +number: 新图层块的数字(也支持纯数字字符串如'1')或id +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 + +setBlock: fn(number: number|string, x: number, y: number, floorId?: string) +转变图块 +例如:core.setBlock(1, 0, 0); // 把地图左上角变成黄墙 +number: 新图块的数字(也支持纯数字字符串如'1')或id +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 + +setBlockFilter: fn(filter?: ?, x?: number, y?: number, floorId?: string) +设置某个点图块的特效 + +setBlockOpacity: fn(opacity?: number, x?: number, y?: number, floorId?: string) +设置某个点图块的不透明度 + +setMapBlockDisabled: fn(floorId?: string, x?: number, y?: number, disabled?: bool) +设置某个点图块的强制启用或禁用状态 + +showBgFgMap: fn(name?: string, loc?: [number]|[[number]], floorId?: string, callback?: fn()) +显示前景/背景地图 + +showBlock: fn(x: number, y: number, floorId?: string) +显示(隐藏或显示的)图块,此函数将被“显示事件”指令和勾选了“不消失”的“移动/跳跃事件”指令(如阻击怪)的终点调用 +例如:core.showBlock(0, 0); // 显示地图左上角的图块 +x: 横坐标 +y: 纵坐标 +floorId: 地图id,不填视为当前地图 + +showFloorImage: fn(loc?: [number]|[[number]], floorId?: string, callback?: fn()) +显示一个楼层贴图 + +stairExists: fn(x: number, y: number, floorId?: string) -> bool +某个点是否存在楼梯 + +stopAnimate: fn(id?: number, doCallback?: bool) +立刻停止一个动画播放 +id: 播放动画的编号,即drawAnimate或drawHeroAnimate的返回值;不填视为停止所有动画 +doCallback: 是否执行该动画的回调函数 + +terrainExists: fn(x: number, y: number, id?: string, floorId?: string) -> bool +某个点是否存在(指定的)地形 + +turnBlock: fn(direction?: string, x?: number, y?: number, floorId?: string) +事件转向 +``` + +## ui.js + +负责一切UI界面的绘制。主要包括三个部分: +- 设置某个画布的属性与在某个画布上绘制的相关API +- 具体的某个UI界面的绘制 +- 动态创建画布相关的API + +```text +calWidth: fn(name: string|CanvasRenderingContext2D, text: string, font?: string) -> number +计算某段文字的宽度 +参考资料:https://www.w3school.com.cn/tags/canvas_measuretext.asp + +clearMap: fn(name: string|CanvasRenderingContext2D, x?: number, y?: number, width?: number, height?: number) +清空某个画布图层 +name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。 +如果name也可以是'all',若为all则为清空所有系统画布。 +参考资料:https://www.w3school.com.cn/tags/canvas_clearrect.asp + +clearUI: fn() +清空UI层内容 + +clearUIEventSelector: fn(codes?: number|[number]) +清除若干个自绘的选择光标 +codes: 清除的光标编号;可以是单个编号或编号数组;不填则清除所有光标 + +closePanel: fn() +结束一切事件和绘制,关闭UI窗口,返回游戏进程 + +createCanvas: fn(name: string, x: number, y: number, width: number, height: number, zIndex: number) -> CanvasRenderingContext2D +动态创建一个画布。 +name: 要创建的画布名,如果已存在则会直接取用当前存在的。 +x,y: 创建的画布相对窗口左上角的像素坐标 +width,height: 创建的长宽。 +zIndex: 创建的纵向高度(关系到画布之间的覆盖),z值高的将覆盖z值低的;系统画布的z值可在个性化中查看。 +返回创建的画布的context,也可以通过core.dymCanvas[name]调用。 + +deleteAllCanvas: fn() +清空所有的自定义画布 + +deleteCanvas: fn(name: string|fn(name: string) -> bool) +删除一个自定义画布 +name: 画布名;也可以传入一个filter对画布名进行筛选。 + +drawArrow: fn(name: string|CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, style?: string, lineWidth?: number) +在某个canvas上绘制一个箭头 + +drawBackground: fn(left: string, top: string, right: string, bottom: string, posInfo?: {px: number, py: number, direction: string}) +绘制一个背景图,可绘制winskin或纯色背景;支持小箭头绘制 + +drawBook: fn(index?: ?) +绘制怪物手册 + +drawChoices: fn(content?: string, choices?: [?], width?: number, ctx?: string|CanvasRenderingContext2D) +绘制一个选项界面 + +drawConfirmBox: fn(text: string, yesCallback?: fn(), noCallback?: fn()) +绘制一个确认框 +此项会打断事件流,如需不打断版本的请使用core.myconfirm() +text: 要绘制的内容,支持 ${} 语法 +yesCallback: 点击确认后的回调 +noCallback: 点击取消后的回调 + +drawFly: fn(page?: ?) +绘制楼层传送器 + +drawIcon: fn(name: string|CanvasRenderingContext2D, id: string, x: number, y: number, w?: number, h?: number, frame?: number) +在某个canvas上绘制一个图标 + +drawImage: fn(name: string|CanvasRenderingContext2D, image: string|image, x: number, y: number, w?: number, h?: number, x1?: number, y1?: number, w1?: number, h1?: number, angle?: number) +在一个画布上绘制图片 +后面的8个坐标参数与canvas的drawImage的八个参数完全相同。 +name: 可以是系统画布之一,也可以是任意自定义动态创建的画布名 画布名称或者画布的context +image: 要绘制的图片,可以是一个全塔属性中定义的图片名(会从images中去获取;支持加':x',':y',':o'翻转),图片本身,或者一个画布。 +angle:旋转角度 +参考资料:http://www.w3school.com.cn/html5/canvas_drawimage.asp + +drawLine: fn(name: string|CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, style?: string, lineWidth?: number) +在某个canvas上绘制一条线 +参考资料:https://www.w3school.com.cn/tags/canvas_lineto.asp + +drawPagination: fn(page?: ?, totalPage?: ?, y?: number) +绘制分页 + +drawScrollText: fn(content: string, time: number, lineHeight?: number, callback?: fn()) +绘制滚动字幕 + +drawStatusBar: fn() +绘制状态栏 + +drawText: fn(contents: string, callback?: fn()) +地图中间绘制一段文字 + +drawTextBox: fn(content: string, showAll?: bool) +绘制一个对话框 + +drawTextContent: fn(ctx: string|CanvasRenderingContext2D, content: string, config: ?) +绘制一段文字到某个画布上面 +ctx: 要绘制到的画布 +content: 要绘制的内容;转义字符不允许保留 \t, \b 和 \f +config: 绘制配置项,目前暂时包含如下内容(均为可选) +left, top:起始点位置;maxWidth:单行最大宽度;color:默认颜色;align:左中右 +fontSize:字体大小;lineHeight:行高;time:打字机间隔;font:字体名 +返回值:绘制信息 + +drawTip: fn(text: string, id?: string, frame?: number) +左上角绘制一段提示 +text: 要提示的字符串,支持${}语法 +id: 要绘制的图标ID +frame: 要绘制该图标的第几帧 + +drawUIEventSelector: fn(code: number, background: string, x: number, y: number, w: number, h: number, z?: number) +自绘一个闪烁的选择光标 +code: 选择光标的编号,必填 +background: 要绘制的光标背景,必须是一个合法的WindowSkin +x, y, w, h: 绘制的坐标和长宽 +z: 可选,光标的的z值 + +drawWaiting: fn(text: string) +绘制等待界面 + +drawWindowSkin: fn(background: string, ctx: string|CanvasRenderingContext2D, x: number, y: number, w: string, h: string, direction?: string, px?: number, py?: number) +绘制WindowSkin + +fillArc: fn(name: string|CanvasRenderingContext2D, x: number, y: number, r: number, start: number, end: number, style?: string) +在某个canvas上绘制一个扇形 +参考资料:https://www.w3school.com.cn/tags/canvas_arc.asp + +fillBoldText: fn(name: string|CanvasRenderingContext2D, text: string, x: number, y: number, style?: string, strokeStyle?: string, font?: string, maxWidth?: number) +在某个画布上绘制一个描边文字 +text: 要绘制的文本 +style: 绘制的样式 +strokeStyle: 要绘制的描边颜色 +font: 绘制的字体 +maxWidth: 最大宽度,超过此宽度会自动放缩 + +fillCircle: fn(name: string|CanvasRenderingContext2D, x: number, y: number, r: number, style?: string) +在某个canvas上绘制一个圆 +参考资料:https://www.w3school.com.cn/tags/canvas_arc.asp + +fillEllipse: fn(name: string|CanvasRenderingContext2D, x: number, y: number, a: number, b: number, angle?: number, style?: string) +在某个canvas上绘制一个椭圆 + +fillPolygon: fn(name: string|CanvasRenderingContext2D, nodes?: [[number]], style?: string) +在某个canvas上绘制一个多边形 + +fillRect: fn(name: string|CanvasRenderingContext2D, x: number, y: number, width: number, height: number, style?: string, angle?: number) +绘制一个矩形。 +x,y: 绘制的坐标 +width,height: 绘制的长宽 +style: 绘制的样式 +angle: 旋转的角度,弧度制,如Math.PI/2代表90度 +参考资料:https://www.w3school.com.cn/tags/canvas_fillrect.asp + +fillRoundRect: fn(name: string|CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number, style?: string, angle?: number) +在某个canvas上绘制一个圆角矩形 + +fillText: fn(name: string|CanvasRenderingContext2D, text: string, x: number, y: number, style?: string, font?: string, maxWidth?: number) +在某个画布上绘制一段文字 +text: 要绘制的文本 +style: 绘制的样式 +font: 绘制的字体 +maxWidth: 最大宽度,超过此宽度会自动放缩 +参考资料:https://www.w3school.com.cn/tags/canvas_filltext.asp + +getContextByName: fn(canvas: string|CanvasRenderingContext2D) -> CanvasRenderingContext2D +根据画布名找到一个画布的context;支持系统画布和自定义画布。如果不存在画布返回null。 +也可以传画布的context自身,则返回自己。 + +getTextContentHeight: fn(content: string, config?: ?) +获得某段文字的预计绘制高度;参数说明详见 drawTextContent + +getToolboxItems: fn(cls: string) -> [string] +获得所有应该在道具栏显示的某个类型道具 + +loadCanvas: fn(name: string|CanvasRenderingContext2D) +加载某个canvas状态 + +relocateCanvas: fn(name: string, x: number, y: number, useDelta: bool) +重新定位一个自定义画布 + +resizeCanvas: fn(name: string, x: number, y: number) +重新设置一个自定义画布的大小 + +rotateCanvas: fn(name: string, angle: number, centerX?: number, centerY?: number) +设置一个自定义画布的旋转角度 +centerX, centerY: 旋转中心(以屏幕像素为基准);不填视为图片正中心。 + +saveCanvas: fn(name: string|CanvasRenderingContext2D) +保存某个canvas状态 + +setAlpha: fn(name: string|CanvasRenderingContext2D, alpha: number) -> number +设置某个canvas接下来绘制的不透明度;不会影响已经绘制的内容 +返回设置之前画布的不透明度。 +如果需要修改画布本身的不透明度请使用setOpacity +参考资料:https://www.w3school.com.cn/tags/canvas_globalalpha.asp + +setFillStyle: fn(name: string|CanvasRenderingContext2D, style: string) +设置某个canvas的绘制属性(如颜色等) +参考资料:https://www.w3school.com.cn/tags/canvas_fillstyle.asp + +setFilter: fn(name: string|CanvasRenderingContext2D, filter: any) +设置某个canvas接下来绘制的filter + +setFont: fn(name: string|CanvasRenderingContext2D, font: string) +设置某个canvas的文字字体 +参考资料:https://www.w3school.com.cn/tags/canvas_font.asp + +setFontForMaxWidth: fn(name: string|CanvasRenderingContext2D, text: string, maxWidth: number, font?: ?) -> string +根据最大宽度自动缩小字体 + +setLineWidth: fn(name: string|CanvasRenderingContext2D, lineWidth: number) +设置某个canvas的线宽度 +参考资料:https://www.w3school.com.cn/tags/canvas_linewidth.asp + +setOpacity: fn(name: string|CanvasRenderingContext2D, opacity: number) +设置某个canvas整体的透明度;此函数直接改变画布本身,对已经绘制的内容也生效 +如果仅想对接下来的绘制生效请使用setAlpha + +setStrokeStyle: fn(name: string|CanvasRenderingContext2D, style: string) +设置某个canvas边框属性 +参考资料:https://www.w3school.com.cn/tags/canvas_strokestyle.asp + +setTextAlign: fn(name: string|CanvasRenderingContext2D, align: string) +设置某个canvas的对齐 +参考资料:https://www.w3school.com.cn/tags/canvas_textalign.asp + +setTextBaseline: fn(name: string|CanvasRenderingContext2D, baseline: string) +设置某个canvas的基准线 +baseline: 可为alphabetic, top, hanging, middle, ideographic, bottom +参考资料:https://www.w3school.com.cn/tags/canvas_textbaseline.asp + +splitLines: fn(name: string|CanvasRenderingContext2D, text: string, maxWidth?: number, font?: string) +字符串自动换行的分割 + +strokeArc: fn(name: string|CanvasRenderingContext2D, x: number, y: number, r: number, start: number, end: number, style?: string, lineWidth?: number) +在某个canvas上绘制一段弧 +参考资料:https://www.w3school.com.cn/tags/canvas_arc.asp + +strokeCircle: fn(name: string|CanvasRenderingContext2D, x: number, y: number, r: ?, style?: string, lineWidth?: number) +在某个canvas上绘制一个圆的边框 +参考资料:https://www.w3school.com.cn/tags/canvas_arc.asp + +strokeEllipse: fn(name: string|CanvasRenderingContext2D, x: number, y: number, a: number, b: number, angle?: number, style?: string, lineWidth?: number) +在某个canvas上绘制一个椭圆的边框 + +strokePolygon: fn(name: string|CanvasRenderingContext2D, nodes?: [[number]], style?: string, lineWidth?: number) +在某个canvas上绘制一个多边形的边框 + +strokeRect: fn(name: string|CanvasRenderingContext2D, x: number, y: number, width: number, height: number, style?: string, lineWidth?: number, angle?: number) +绘制一个矩形的边框 +style: 绘制的样式 +lineWidth: 线宽 +angle: 旋转角度,弧度制,如Math.PI/2为90度 +参考资料:https://www.w3school.com.cn/tags/canvas_strokerect.asp + +strokeRoundRect: fn(name: string|CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number, style?: string, lineWidth?: number, angle?: number) +在某个canvas上绘制一个圆角矩形的边框 + +textImage: fn(content: string, lineHeight?: number) -> image +文本图片化 +``` + +## utils.js + +工具函数库,里面有各个样板中使用到的工具函数。 + +```text +applyEasing: fn(mode?: string) -> fn(t: number) -> number +获得变速移动曲线 + +arrayToRGB: fn(color: [number]) -> string +颜色数组转字符串 +例如:core.arrayToRGB([102, 204, 255]); // "#66ccff" +color: 一行三列的数组,必须为不大于255的自然数 +返回值:该颜色的#xxxxxx字符串表示 + +arrayToRGBA: fn(color: [number]) -> string +颜色数组转字符串 +例如:core.arrayToRGBA([102, 204, 255, 0.3]); // "rgba(102,204,255,0.3)" +color: 一行三列或一行四列的数组,前三个元素必须为不大于255的自然数。第四个元素(如果有)必须为0或不大于1的数字,第四个元素不填视为1 +返回值:该颜色的rgba(...)字符串表示 + +calValue: fn(value: string, prefix?: string) +计算一个表达式的值,支持status:xxx等的计算。 +例如:core.calValue('status:hp + status:def'); // 计算主角的生命值加防御力 +value: 待求值的表达式 +prefix: 独立开关前缀,一般可省略 +返回值:求出的值 + +clamp: fn(x: number, a: number, b: number) -> number +将x限定在[a,b]区间内,注意a和b可交换 +例如:core.clamp(1200, 1, 1000); // 1000 +x: 原始值,!x为true时x一律视为0 +a: 下限值,大于b将导致与b交换 +b: 上限值,小于a将导致与a交换 + +clone: fn(data?: ?, filter?: fn(name: string, value: ?) -> bool, recursion?: bool) +深拷贝一个对象(函数将原样返回) +例如:core.clone(core.status.hero, (name, value) => (name == 'items' || typeof value == 'number'), false); // 深拷贝主角的属性和道具 +data: 待拷贝对象 +filter: 过滤器,可选,表示data为数组或对象时拷贝哪些项或属性,true表示拷贝 +recursion: 过滤器是否递归,可选。true表示过滤器也被递归 +返回值:拷贝的结果,注意函数将原样返回 + +cloneArray: fn(data?: [number]|[[number]]) -> [number]|[[number]] +深拷贝一个1D或2D数组对象 +例如:core.cloneArray(core.status.thisMap.map) + +copy: fn(data: string) -> bool +尝试复制一段文本到剪切板。 + +decodeBase64: fn(str: string) -> string +base64解密 +例如:core.decodeBase64('YWJjZA=='); // "abcd" +str: 密文 +返回值:明文 + +decodeRoute: fn(route: string) -> [string] +录像解压的最后一步,即一压的逆过程 +例如:core.decodeRoute(core.encodeRoute(core.status.route)); // 一压当前录像再解压-_-| +route: 录像解压倒数第二步的结果,即一压的结果 +返回值:原始录像 + +decompress: fn(value: ?) +解压缩一个数据 + +download: fn(filename: string, content: string) +弹窗请求下载一个文本文件 +例如:core.download('route.txt', JSON.stringify(core.status.route)); // 弹窗请求下载录像 +filename: 文件名 +content: 文件内容 + +encodeBase64: fn(str: string) -> string +base64加密 +例如:core.encodeBase64('abcd'); // 'YWJjZA==' +str: 明文 +返回值:密文 + +encodeRoute: fn(route: [string]) -> string +录像压缩缩 +例如:core.encodeRoute(core.status.route); // 压缩当前录像 +route: 原始录像,自定义内容(不予压缩,原样写入)必须由0-9A-Za-z和下划线、冒号组成,所以中文和数组需要用JSON.stringify预处理再base64压缩才能交由一压 +返回值:一压的结果 + +formatBigNumber: fn(x: number, onMap?: bool) -> string +大数字格式化,单位为10000的倍数(w,e,z,j,g),末尾四舍五入 +例如:core.formatBigNumber(123456789, false); // "12346w" +x: 原数字 +onMap: 可选,true表示用于地图显伤,结果总字符数最多为5,否则最多为6 +返回值:格式化结果 + +formatDate: fn(date: ?) -> string +格式化日期为字符串 + +formatDate2: fn(date: ?) -> string +格式化日期为最简字符串 + +formatSize: fn(size: number) -> string +格式化文件大小 + +formatTime: fn(time: number) -> string +格式化时间 + +getCookie: fn(name: string) -> string +访问浏览器cookie + +getGlobal: fn(key: string, defaultValue?: ?) +读取一个全局存储,适用于global:xxx,支持录像。 +例如:if (core.getGlobal('一周目已通关', false) === true) core.getItem('dagger'); // 二周目游戏进行到此处时会获得一把屠龙匕首 +key: 全局变量名称,支持中文 +defaultValue: 可选,当此全局变量不存在或值为null、undefined时,用此值代替 +返回值:全局变量的值 + +getGuid: fn() -> string +获得或生成浏览器唯一的guid + +getLocalForage: fn(key: string, defaultValue?: ?, successCallback?: fn(data: ?), errorCallback?: fn()) +从本地数据库读出一段数据 + +getLocalStorage: fn(key: string, defaultValue?: ?) +获得本地存储 + +hideWithAnimate: fn(obj?: ?, speed?: number, callback?: fn()) +动画使某对象消失 + +http: fn(type: string, url: string, formData: ?, success?: fn(data: string), error?: fn(message: string), mimeType?: string, responseType?: string, onprogress?: fn(loaded: number, total: number)) +发送一个HTTP请求 [异步] +type: 请求类型,只能为GET或POST +url: 目标地址 +formData: 如果是POST请求则为表单数据 +success: 成功后的回调 +error: 失败后的回调 + +inArray: fn(array?: ?, element?: ?) -> bool +判定array是不是一个数组,以及element是否在该数组中。 +array: 可能的数组,不为数组或不填将导致返回值为false +element: 待查找的元素 +返回值:如果array为数组且具有element这项,就返回true,否则返回false + +isset: fn(v?: ?) -> bool +判断一个值是否不为null,undefined和NaN +例如:core.isset(0/0); // false,因为0/0等于NaN +v: 待测值,可选 +返回值:false表示待测值为null、undefined、NaN或未填写,true表示为其他值。 + +matchRegex: fn(pattern: string, string: string) -> string +是否满足正则表达式 + +matchWildcard: fn(pattern: string, string: string) -> bool +通配符匹配,用于搜索图块等批量处理。 +例如:core.playSound(core.matchWildcard('*Key', itemId) ? 'item.mp3' : 'door.mp3'); // 判断捡到的是钥匙还是别的道具,从而播放不同的音效 +pattern: 模式串,每个星号表示任意多个(0个起)字符 +string: 待测串 +返回值:true表示匹配成功,false表示匹配失败 + +myconfirm: fn(hint: string, yesCallback?: fn(), noCallback?: fn()) +显示确认框,类似core.drawConfirmBox(),但不打断事件流 +例如:core.myconfirm('重启游戏?', core.restart); // 弹窗询问玩家是否重启游戏 +hint: 弹窗的内容,支持 ${} 语法 +yesCallback: 确定后的回调函数 +noCallback: 取消后的回调函数,可选 + +myprompt: fn(hint: string, value: string, callback?: fn(data?: string)) +让用户输入一段文字 + +push: fn(a: [?], b: ?) -> [?] +将b(可以是另一个数组)插入数组a的末尾,此函数用于弥补a.push(b)中b只能是单项的不足。 +例如:core.push(todo, {type: 'unfollow'}); // 在事件指令数组todo的末尾插入“取消所有跟随者”指令 +a: 原数组 +b: 待插入的新末项或后缀数组 +返回值:插入完毕后的新数组,它是改变原数组a本身得到的 + +rand: fn(num?: number) -> number +不支持SL的随机数 +例如:1 + core.rand(6); // 随机生成一个小于7的正整数,模拟骰子的效果 +num: 填正数表示生成小于num的随机自然数,否则生成小于1的随机正数 +返回值:随机数,即使读档也不会改变结果 + +rand2: fn(num?: number) -> number +支持SL的随机数,并计入录像 +例如:1 + core.rand2(6); // 随机生成一个小于7的正整数,模拟骰子的效果 +num: 正整数,0或不填会被视为2147483648 +返回值:属于 [0, num) 的随机数 + +readFile: fn(success?: fn(data: string), error?: fn(message: string), readType?: bool) +尝试请求读取一个本地文件内容 [异步] +success: 成功后的回调 +error: 失败后的回调 +readType: 不设置则以文本读取,否则以DataUrl形式读取 + +readFileContent: fn(content: string) +文件读取完毕后的内容处理 [异步] + +removeLocalForage: fn(key: string, successCallback?: fn(), errorCallback?: fn()) +移除本地数据库的数据 + +removeLocalStorage: fn(key: string) +移除本地存储 + +replaceText: fn(text: string, prefix?: string) -> string +将一段文字中的${}(表达式)进行替换。 +例如:core.replaceText('衬衫的价格是${status:hp}镑${item:yellowKey}便士。'); // 把主角的生命值和持有的黄钥匙数量代入这句话 +text: 模板字符串,可以使用${}计算js表达式,支持“状态、物品、变量、独立开关、全局存储、图块id、图块类型、敌人数据、装备id”等量参与运算 +返回值:替换完毕后的字符串 + +replaceValue: fn(value: string) -> string +对一个表达式中的特殊规则进行替换,如status:xxx等。 +例如:core.replaceValue('status:atk+item:yellowKey'); // 把这两个冒号表达式替换为core.getStatus('hp')和core.itemCount('yellowKey')这样的函数调用 +value: 模板字符串,注意独立开关不会被替换 +返回值:替换完毕后的字符串 + +same: fn(a?: ?, b?: ?) -> bool +判定深层相等, 会逐层比较每个元素 +例如:core.same(['1', 2], ['1', 2]); // true + +setGlobal: fn(key: string, value?: ?) +设置一个全局存储,适用于global:xxx,录像播放时将忽略此函数。 +例如:core.setBlobal('一周目已通关', true); // 设置全局存储“一周目已通关”为true,方便二周目游戏中的新要素。 +key: 全局变量名称,支持中文 +value: 全局变量的新值,不填或null表示清除此全局存储 + +setLocalForage: fn(key: string, value?: ?, successCallback?: fn(), errorCallback?: fn()) +往数据库写入一段数据 + +setLocalStorage: fn(key: string, value?: ?) +设置本地存储 + +setStatusBarInnerHTML: fn(name: string, value: ?, css?: string) +填写非自绘状态栏 +例如:core.setStatusBarInnerHTML('hp', core.status.hero.hp, 'color: #66CCFF'); // 更新状态栏中的主角生命,使用加载画面的宣传色 +name: 状态栏项的名称,如'hp', 'atk', 'def'等。必须是core.statusBar中的一个合法项 +value: 要填写的内容,大数字会被格式化为至多6个字符,无中文的内容会被自动设为斜体 +css: 额外的css样式,可选。如更改颜色等 + +setTwoDigits: fn(x: number) -> string +两位数显示 + +showWithAnimate: fn(obj?: ?, speed?: number, callback?: fn()) +动画显示某对象 + +splitImage: fn(image?: string|image, width?: number, height?: number) -> [image] +等比例切分一张图片 +例如:core.splitImage(core.material.images.images['npc48.png'], 32, 48); // 把npc48.png切分成若干32×48px的小人 +image: 图片名(支持映射前的中文名)或图片对象(参见上面的例子),获取不到时返回[] +width: 子图的宽度,单位为像素。原图总宽度必须是其倍数,不填视为32 +height: 子图的高度,单位为像素。原图总高度必须是其倍数,不填视为正方形 +返回值:子图组成的数组,在原图中呈先行后列,从左到右、从上到下排列。 + +strlen: fn(str: string) -> number +求字符串的国标码字节数,也可用于等宽字体下文本的宽度测算。请注意样板的默认字体Verdana不是等宽字体 +例如:core.strlen('无敌ad'); // 6 +str: 待测字符串 +返回值:字符串的国标码字节数,每个汉字为2,每个ASCII字符为1 + +subarray: fn(a?: [?], b?: [?]) -> [?]|null +判定一个数组是否为另一个数组的前缀,用于录像接续播放。请注意函数名没有大写字母 +例如:core.subarray(['ad', '米库', '小精灵', '小破草', '小艾'], ['ad', '米库', '小精灵']); // ['小破草', '小艾'] +a: 可能的母数组,不填或比b短将返回null +b: 可能的前缀,不填或比a长将返回null +返回值:如果b不是a的前缀将返回null,否则将返回a去掉此前缀后的剩余数组 + +turnDirection: fn(turn: string, direction?: string) -> string +计算应当转向某个方向 +turn: 转向的方向,可为 up,down,left,right,:left,:right,:back 七种 +direction: 当前方向 + +unshift: fn(a: [?], b: ?) -> [?] +将b(可以是另一个数组)插入数组a的开头,此函数用于弥补a.unshift(b)中b只能是单项的不足。 +例如:core.unshift(todo, {type: 'unfollow'}); // 在事件指令数组todo的开头插入“取消所有跟随者”指令 +a: 原数组 +b: 待插入的新首项或前缀数组 +返回值:插入完毕后的新数组,它是改变原数组a本身得到的 + +unzip: fn(blobOrUrl?: ?, success?: fn(data: ?), error?: fn(error: string), convertToText?: bool, onprogress?: fn(loaded: number, total: number)) +解压一段内容 +``` + +## plugin.js + +插件编写中内置了一些常用的插件。 + +```text +autoRemoveMaps: fn(floorId: string) +根据楼层分区信息自动砍层与恢复 + +canOpenShop: fn(id: string) -> bool +当前能否打开某个商店 + +canUseQuickShop: fn(id: string) -> string +当前能否使用某个快捷商店 +如果返回一个字符串,则代表不能,返回的字符串作为不能的提示;返回null表示可以使用 + +drawLight: fn(name: string|CanvasRenderingContext2D, color?: number, lights?: [[number]], lightDec?: number) +绘制一段灯光效果 +name:必填,要绘制到的画布名;可以是一个系统画布,或者是个自定义画布;如果不存在则创建 +color:可选,只能是一个0~1之间的数,为不透明度的值。不填则默认为0.9。 +lights:可选,一个数组,定义了每个独立的灯光。其中每一项是三元组 [x,y,r] x和y分别为该灯光的横纵坐标,r为该灯光的半径。 +lightDec:可选,0到1之间,光从多少百分比才开始衰减(在此范围内保持全亮),不设置默认为0。比如lightDec为0.5代表,每个灯光部分内圈50%的范围全亮,50%以后才开始快速衰减。 +例如:core.plugin.drawLight('test', 0.2, [[25,11,46,0.1]]); // 创建一个test图层,不透明度0.2,其中在(25,11)点存在一个半径为46的灯光效果,灯光中心不透明度0.1。 +core.plugin.drawLight('test2', 0.9, [[25,11,46],[105,121,88],[301,221,106]]); // 创建test2图层,且存在三个灯光效果,分别是中心(25,11)半径46,中心(105,121)半径88,中心(301,221)半径106。 + +isShopVisited: fn(id: string) -> bool +某个全局商店是否被访问过 + +listShopIds: fn() -> [string] +列出所有应当显示的快捷商店列表 + +openItemShop: fn(itemShopId: string) +打开一个道具商店 + +openShop: fn(shopId: string, noRoute?: bool) +打开一个全局商店 +shopId: 要开启的商店ID +noRoute: 打开行为是否不计入录像 + +removeMaps: fn(fromId: string, toId?: string) +删除某一些楼层;删除后不会存入存档,不可浏览地图也不可飞到。 +fromId: 开始删除的楼层ID +toId: 删除到的楼层编号;可选,不填则视为fromId +例如:core.removeMaps("MT1", "MT300") 删除MT1~MT300之间的全部层 +core.removeMaps("MT10") 只删除MT10层 + +resumeMaps: fn(fromId: string, toId?: string) +恢复某一些被删除楼层。 +fromId: 开始恢复的楼层ID +toId: 恢复到的楼层编号;可选,不填则视为fromId +例如:core.resumeMaps("MT1", "MT300") 恢复MT1~MT300之间的全部层 +core.resumeMaps("MT10") 只删恢复MT10层 + +setShopVisited: fn(id: string, visited?: bool) +设置某个商店的访问状态 +``` diff --git a/_docs/blocksdemo.md b/_docs/blocksdemo.md new file mode 100644 index 0000000..8cec8c8 --- /dev/null +++ b/_docs/blocksdemo.md @@ -0,0 +1,55 @@ +http://127.0.0.1:1055/_docs/#/blocksdemo + +方便测试用 + +这种写法不会生成按钮 +```js +'run'; +return bg.parseList(['行内']); +// return bg.parse(['asdasd','df'],'event')+'
'+bg.parseList(['asfewg','sty']); +``` +并且是行内的 + +这种写法之后的版本会补一个按钮'添加到常用事件', 目前先这样吧 + +``` MotaAction.event +[ + '显示文章', + '...' +] +``` + +``` MotaAction.shop + [{ + "id": "shop1", + "text": "\t[贪婪之神,moneyShop]勇敢的武士啊, 给我${20+2*flag:shop1}金币就可以:", + "textInList": "1F金币商店", + "choices": [ + {"text": "生命+800", "need": "status:money>=20+2*flag:shop1", "action": [ + {"type": "setValue", "name": "status:money", "operator": "-=", "value": "20+2*flag:shop1"}, + {"type": "setValue", "name": "flag:shop1", "operator": "+=", "value": "1"}, + {"type": "setValue", "name": "status:hp", "operator": "+=", "value": "800"} + ]} + ] + },{ + "id": "itemShop", + "item": true, + "textInList": "道具商店", + "choices": [ + {"id": "yellowKey", "number": 10, "money": 10} + ] + },{ + "id": "keyShop1", + "textInList": "回收钥匙商店", + "commonEvent": "回收钥匙商店", + "args": "" + }] +``` + +``` MotaAction.action +'显示文章' +``` + +``` MotaAction +['MotaAction.action的anction可以省略','只写MotaAction'] +``` \ No newline at end of file diff --git a/_docs/docsify.min.js b/_docs/docsify.min.js new file mode 100644 index 0000000..4c22486 --- /dev/null +++ b/_docs/docsify.min.js @@ -0,0 +1,2 @@ +!function(){"use strict";function e(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}function t(e){return"string"==typeof e||"number"==typeof e}function n(){}function r(e){return"function"==typeof e}function i(e){var t=["init","mounted","beforeEach","afterEach","doneEach","ready"];e._hooks={},e._lifecycle={},t.forEach(function(t){var n=e._hooks[t]=[];e._lifecycle[t]=function(e){return n.push(e)}})}function o(e,t,r,i){void 0===i&&(i=n);var o=e._hooks[t],a=function(e){var t=o[e];if(e>=o.length)i(r);else if("function"==typeof t)if(2===t.length)t(r,function(t){r=t,a(e+1)});else{var n=t(r);r=void 0!==n?n:r,a(e+1)}else a(e+1)};a(0)}function a(e,t){if(void 0===t&&(t=!1),"string"==typeof e){if(void 0!==window.Vue)return s(e);e=t?s(e):fe[e]||(fe[e]=s(e))}return e}function s(e,t){return t?e.querySelector(t):ge.querySelector(e)}function l(e,t){return[].slice.call(t?e.querySelectorAll(t):ge.querySelectorAll(e))}function u(e,t){return e=ge.createElement(e),t&&(e.innerHTML=t),e}function c(e,t){return e.appendChild(t)}function h(e,t){return e.insertBefore(t,e.children[0])}function p(e,t,n){r(t)?window.addEventListener(e,t):e.addEventListener(t,n)}function d(e,t,n){r(t)?window.removeEventListener(e,t):e.removeEventListener(t,n)}function f(e,t,n){e&&e.classList[n?t:"toggle"](n||t)}function g(e){c(ve,u("style",e))}function m(e){return e?(/\/\//.test(e)||(e="https://github.com/"+e),''):""}function v(e){var t='';return(ke?t+"
":"
"+t)+'
\x3c!--main--\x3e
'}function y(){var e=", 100%, 85%";return'
'}function b(e,t){return void 0===t&&(t=""),e&&e.length?(e.forEach(function(e){t+='
  • '+e.title+"
  • ",e.children&&(t+='
  • ")}),t):""}function k(e,t){return'

    '+t.slice(5).trim()+"

    "}function w(e){return""}function x(){var e=u("div");e.classList.add("progress"),c(me,e),pe=e}function _(e,t){void 0===t&&(t=!1);var r=new XMLHttpRequest,i=function(){r.addEventListener.apply(r,arguments)},o=_e[e];return o?{then:function(e){return e(o.content,o.opt)},abort:n}:(r.open("GET",e),r.send(),{then:function(o,a){if(void 0===a&&(a=n),t){var s=setInterval(function(e){return xe({step:Math.floor(5*Math.random()+1)})},500);i("progress",xe),i("loadend",function(e){xe(e),clearInterval(s)})}i("error",a),i("load",function(t){var n=t.target;if(n.status>=400)a(n);else{var i=_e[e]={content:n.response,opt:{updatedAt:r.getResponseHeader("last-modified")}};o(i.content,i.opt)}})},abort:function(e){return 4!==r.readyState&&r.abort()}})}function S(e,t){e.innerHTML=e.innerHTML.replace(/var\(\s*--theme-color.*?\)/g,t)}function C(e,t){return t={exports:{}},e(t,t.exports),t.exports}function L(e,t){var n=[],r={};return e.forEach(function(e){var i=e.level||1,o=i-1;i>t||(r[o]?r[o].children=(r[o].children||[]).concat(e):n.push(e),r[i]=e)}),n}function E(e){return e.toLowerCase()}function T(e){if("string"!=typeof e)return"";var t=e.trim().replace(/[A-Z]+/g,E).replace(/<[^>\d]+>/g,"").replace(Oe,"").replace(/\s/g,"-").replace(/-+/g,"-").replace(/^(\d)/,"_$1"),n=Pe[t];return n=Pe.hasOwnProperty(t)?n+1:0,Pe[t]=n,n&&(t=t+"-"+n),t}function $(e,t){return''+t+''}function A(e){return e.replace(/<(pre|template|code)[^>]*?>[\s\S]+?<\/(pre|template|code)>/g,function(e){return e.replace(/:/g,"__colon__")}).replace(/:(\w+?):/gi,be&&window.emojify||$).replace(/__colon__/g,":")}function P(e){var t={};return(e=e.trim().replace(/^(\?|#|&)/,""))?(e.split("&").forEach(function(e){var n=e.replace(/\+/g," ").split("=");t[n[0]]=n[1]&&je(n[1])}),t):t}function O(e,t){void 0===t&&(t=[]);var n=[];for(var r in e)t.indexOf(r)>-1||n.push(e[r]?(Me(r)+"="+Me(e[r])).toLowerCase():Me(r));return n.length?"?"+n.join("&"):""}function j(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];return Re(e.join("/"))}function M(e){void 0===e&&(e="");var t={};return e&&(e=e.replace(/:([\w-]+)=?([\w-]+)?/g,function(e,n,r){return t[n]=r||!0,""}).trim()),{str:e,config:t}}function q(e,t){var n=function(e){return me.classList.toggle("close")};e=a(e),p(e,"click",function(e){e.stopPropagation(),n()});var r=a(".sidebar");ke&&p(me,"click",function(e){return me.classList.contains("close")&&n()}),p(r,"click",function(e){return setTimeout(0)})}function N(){var e=a("section.cover");if(e){var t=e.getBoundingClientRect().height;window.pageYOffset>=t||e.classList.contains("hidden")?f(me,"add","sticky"):f(me,"remove","sticky")}}function R(e,t,n,r){t=a(t);var i,o=l(t,"a"),s=e.toURL(e.getCurrentPath());return o.sort(function(e,t){return t.href.length-e.href.length}).forEach(function(e){var t=e.getAttribute("href"),r=n?e.parentNode:e;0!==s.indexOf(t)||i?f(r,"remove","active"):(i=e,f(r,"add","active"))}),r&&(ge.title=i?i.innerText+" - "+ze:ze),i}function F(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function H(e){Ue&&Ue.stop(),Ye=!1,Ue=new Ie({start:window.pageYOffset,end:e.getBoundingClientRect().top+window.pageYOffset,duration:500}).on("tick",function(e){return window.scrollTo(0,e)}).on("done",function(){Ye=!0,Ue=null}).begin()}function z(e){if(Ye){for(var t,n=a(".sidebar"),r=l(".anchor"),i=s(n,".sidebar-nav"),o=s(n,"li.active"),u=document.documentElement,c=(u&&u.scrollTop||document.body.scrollTop)-Ze,h=0,p=r.length;hc){t||(t=d);break}t=d}if(t){var f=De[B(e,t.getAttribute("data-id"))];if(f&&f!==o&&(o&&o.classList.remove("active"),f.classList.add("active"),o=f,!We&&me.classList.contains("sticky"))){var g=n.clientHeight,m=o.offsetTop+o.clientHeight+40,v=o.offsetTop>=i.scrollTop&&m<=i.scrollTop+g,y=m-0script").filter(function(e){return!/template/.test(e.type)})[0];if(!e)return!1;var t=e.innerText.trim();if(!t)return!1;setTimeout(function(e){window.__EXECUTE_RESULT__=new Function(t)()},0)}function Y(e,t,n){return t="function"==typeof n?n(t):"string"==typeof n?Ee(n)(new Date(t)):t,e.replace(/{docsify-updated}/g,t)}function Z(e){e||(e="not found"),this._renderTo(".markdown-section",e),!this.config.loadSidebar&&this._renderSidebar(),!1===this.config.executeScript||void 0===window.Vue||U()?this.config.executeScript&&U():setTimeout(function(e){var t=window.__EXECUTE_RESULT__;t&&t.$destroy&&t.$destroy(),window.__EXECUTE_RESULT__=(new window.Vue).$mount("#main")},0)}function G(e){var n=a(".app-name-link"),r=e.config.nameLink,i=e.route.path;if(n)if(t(e.config.nameLink))n.setAttribute("href",r);else if("object"==typeof r){var o=Object.keys(r).filter(function(e){return i.indexOf(e)>-1})[0];n.setAttribute("href",r[o])}}function X(e){var t=e.config;e.compiler=new He(t,e.router);var n=t.el||"#app",r=s("nav")||u("nav"),i=s(n),o="",a=me;i?(t.repo&&(o+=m(t.repo)),t.coverpage&&(o+=y()),o+=v(t),e._renderTo(i,o,!0)):e.rendered=!0,t.mergeNavbar&&ke?a=s(".sidebar"):(r.classList.add("app-nav"),t.repo||r.classList.add("no-badge")),h(a,r),t.themeColor&&(ge.head.appendChild(u("div",w(t.themeColor)).firstElementChild),Se(t.themeColor)),e._updateRender(),f(me,"ready")}function V(e,t,n){var r=Object.keys(t).filter(function(t){return(Xe[t]||(Xe[t]=new RegExp("^"+t+"$"))).test(e)&&e!==n})[0];return r?V(e.replace(Xe[r],t[r]),t,e):e}function J(e){return/\.(md|html)$/g.test(e)?e:/\/$/g.test(e)?e+"README.md":e+".md"}function Q(e){var t=location.href.indexOf("#");location.replace(location.href.slice(0,t>=0?t:0)+"#"+e)}function K(e){e.router.normalize(),e.route=e.router.parse(),me.setAttribute("data-page",e.route.file)}function ee(e){var t,n=e.config,r=n.routerMode||"hash";t="history"===r&&we?new Ke(n):new Qe(n),e.router=t,K(e),et=e.route,t.onchange(function(t){if(K(e),e._updateRender(),et.path===e.route.path)return void e.$resetEvents();e.$fetch(),et=e.route})}function te(e){q("button.sidebar-toggle",e.router),e.config.coverpage?!ke&&p("scroll",N):me.classList.add("sticky")}function ne(e,t,n,r,i,o){e=o?e:e.replace(/\/$/,""),(e=Ne(e))&&_(i.router.getFile(e+n)+t).then(r,function(o){return ne(e,t,n,r,i)})}function re(e){var t=e.config,n=t.loadSidebar;if(e.rendered){var r=R(e.router,".sidebar-nav",!0,!0);n&&r&&(r.parentNode.innerHTML+=window.__SUB_SIDEBAR__),e._bindEventOnRendered(r),e._fetchCover(),e.$resetEvents(),o(e,"doneEach"),o(e,"ready")}else e.$fetch(function(t){return o(e,"ready")})}function ie(e){[].concat(e.config.plugins).forEach(function(t){return r(t)&&t(e._lifecycle,e)})}function oe(){this._init()}var ae=e(function(e){return e.replace(/([A-Z])/g,function(e){return"-"+e.toLowerCase()})}),se=Object.assign||function(e){for(var t=arguments,n=Object.prototype.hasOwnProperty,r=1;r80?80:t):t=Math.floor(n/r*100),pe.style.opacity=1,pe.style.width=t>=95?"100%":t+"%",t>=95&&(clearTimeout(de),de=setTimeout(function(e){pe.style.opacity=0,pe.style.width="0%"},200))},_e={},Se=function(e){if(!(window.CSS&&window.CSS.supports&&window.CSS.supports("(--v:red)"))){var t=l("style:not(.inserted),link");[].forEach.call(t,function(t){if("STYLE"===t.nodeName)S(t,e);else if("LINK"===t.nodeName){var n=t.getAttribute("href");if(!/\.css$/.test(n))return;_(n).then(function(t){var n=u("style",t);ve.appendChild(n),S(n,e)})}})}},Ce=/([^{]*?)\w(?=\})/g,Le={YYYY:"getFullYear",YY:"getYear",MM:function(e){return e.getMonth()+1},DD:"getDate",HH:"getHours",mm:"getMinutes",ss:"getSeconds"},Ee=function(e){var t=[],n=0;return e.replace(Ce,function(r,i,o){t.push(e.substring(n,o-1)),n=o+=r.length+1,t.push(function(e){return("00"+("string"==typeof Le[r]?e[Le[r]]():Le[r](e))).slice(-r.length)})}),n!==e.length&&t.push(e.substring(n)),function(e){for(var n="",r=0,i=e||new Date;r/g,">").replace(/"/g,""").replace(/'/g,"'")}function a(e){return e.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g,function(e,t){return t=t.toLowerCase(),"colon"===t?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}function s(e,t){return e=e.source,t=t||"",function n(r,i){return r?(i=i.source||i,i=i.replace(/(^|[^\[])\^/g,"$1"),e=e.replace(r,i),n):new RegExp(e,t)}}function l(){}function u(e){for(var t,n,r=arguments,i=1;iAn error occured:

    "+o(e.message+"",!0)+"
    ";throw e}}var h={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:l,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:l,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:l,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};h.bullet=/(?:[*+-]|\d+\.)/,h.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,h.item=s(h.item,"gm")(/bull/g,h.bullet)(),h.list=s(h.list)(/bull/g,h.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+h.def.source+")")(),h.blockquote=s(h.blockquote)("def",h.def)(),h._tag="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b",h.html=s(h.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,h._tag)(),h.paragraph=s(h.paragraph)("hr",h.hr)("heading",h.heading)("lheading",h.lheading)("blockquote",h.blockquote)("tag","<"+h._tag)("def",h.def)(),h.normal=u({},h),h.gfm=u({},h.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),h.gfm.paragraph=s(h.paragraph)("(?!","(?!"+h.gfm.fences.source.replace("\\1","\\2")+"|"+h.list.source.replace("\\1","\\3")+"|")(),h.tables=u({},h.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/}),t.rules=h,t.lex=function(e,n){return new t(n).lex(e)},t.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},t.prototype.token=function(e,t,n){for(var r,i,o,a,s,l,u,c,p,d=this,e=e.replace(/^ +$/gm,"");e;)if((o=d.rules.newline.exec(e))&&(e=e.substring(o[0].length),o[0].length>1&&d.tokens.push({type:"space"})),o=d.rules.code.exec(e))e=e.substring(o[0].length),o=o[0].replace(/^ {4}/gm,""),d.tokens.push({type:"code",text:d.options.pedantic?o:o.replace(/\n+$/,"")});else if(o=d.rules.fences.exec(e))e=e.substring(o[0].length),d.tokens.push({type:"code",lang:o[2],text:o[3]||""});else if(o=d.rules.heading.exec(e))e=e.substring(o[0].length),d.tokens.push({type:"heading",depth:o[1].length,text:o[2]});else if(t&&(o=d.rules.nptable.exec(e))){for(e=e.substring(o[0].length),l={type:"table",header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/\n$/,"").split("\n")},c=0;c ?/gm,""),d.token(o,t,!0),d.tokens.push({type:"blockquote_end"});else if(o=d.rules.list.exec(e)){for(e=e.substring(o[0].length),a=o[2],d.tokens.push({type:"list_start",ordered:a.length>1}),o=o[0].match(d.rules.item),r=!1,p=o.length,c=0;c1&&s.length>1||(e=o.slice(c+1).join("\n")+e,c=p-1)),i=r||/\n\n(?!\s*$)/.test(l),c!==p-1&&(r="\n"===l.charAt(l.length-1),i||(i=r)),d.tokens.push({type:i?"loose_item_start":"list_item_start"}),d.token(l,!1,n),d.tokens.push({type:"list_item_end"});d.tokens.push({type:"list_end"})}else if(o=d.rules.html.exec(e))e=e.substring(o[0].length),d.tokens.push({type:d.options.sanitize?"paragraph":"html",pre:!d.options.sanitizer&&("pre"===o[1]||"script"===o[1]||"style"===o[1]),text:o[0]});else if(!n&&t&&(o=d.rules.def.exec(e)))e=e.substring(o[0].length),d.tokens.links[o[1].toLowerCase()]={href:o[2],title:o[3]};else if(t&&(o=d.rules.table.exec(e))){for(e=e.substring(o[0].length),l={type:"table",header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/(?: *\| *)?\n$/,"").split("\n")},c=0;c])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:l,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:l,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/,p.link=s(p.link)("inside",p._inside)("href",p._href)(),p.reflink=s(p.reflink)("inside",p._inside)(),p.normal=u({},p),p.pedantic=u({},p.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/}),p.gfm=u({},p.normal,{escape:s(p.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:s(p.text)("]|","~]|")("|","|https?://|")()}),p.breaks=u({},p.gfm,{br:s(p.br)("{2,}","*")(),text:s(p.gfm.text)("{2,}","*")()}),n.rules=p,n.output=function(e,t,r){return new n(t,r).output(e)},n.prototype.output=function(e){for(var t,n,r,i,a=this,s="";e;)if(i=a.rules.escape.exec(e))e=e.substring(i[0].length),s+=i[1];else if(i=a.rules.autolink.exec(e))e=e.substring(i[0].length),"@"===i[2]?(n=":"===i[1].charAt(6)?a.mangle(i[1].substring(7)):a.mangle(i[1]),r=a.mangle("mailto:")+n):(n=o(i[1]),r=n),s+=a.renderer.link(r,null,n);else if(a.inLink||!(i=a.rules.url.exec(e))){if(i=a.rules.tag.exec(e))!a.inLink&&/^/i.test(i[0])&&(a.inLink=!1),e=e.substring(i[0].length),s+=a.options.sanitize?a.options.sanitizer?a.options.sanitizer(i[0]):o(i[0]):i[0];else if(i=a.rules.link.exec(e))e=e.substring(i[0].length),a.inLink=!0,s+=a.outputLink(i,{href:i[2],title:i[3]}),a.inLink=!1;else if((i=a.rules.reflink.exec(e))||(i=a.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),!(t=a.links[t.toLowerCase()])||!t.href){s+=i[0].charAt(0),e=i[0].substring(1)+e;continue}a.inLink=!0,s+=a.outputLink(i,t),a.inLink=!1}else if(i=a.rules.strong.exec(e))e=e.substring(i[0].length),s+=a.renderer.strong(a.output(i[2]||i[1]));else if(i=a.rules.em.exec(e))e=e.substring(i[0].length),s+=a.renderer.em(a.output(i[2]||i[1]));else if(i=a.rules.code.exec(e))e=e.substring(i[0].length),s+=a.renderer.codespan(o(i[2],!0));else if(i=a.rules.br.exec(e))e=e.substring(i[0].length),s+=a.renderer.br();else if(i=a.rules.del.exec(e))e=e.substring(i[0].length),s+=a.renderer.del(a.output(i[1]));else if(i=a.rules.text.exec(e))e=e.substring(i[0].length),s+=a.renderer.text(o(a.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else e=e.substring(i[0].length),n=o(i[1]),r=n,s+=a.renderer.link(r,null,n);return s},n.prototype.outputLink=function(e,t){var n=o(t.href),r=t.title?o(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,o(e[1]))},n.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014\/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014\/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},n.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,i=0;i.5&&(t="x"+t.toString(16)),n+="&#"+t+";";return n},r.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?'
    '+(n?e:o(e,!0))+"\n
    \n":"
    "+(n?e:o(e,!0))+"\n
    "},r.prototype.blockquote=function(e){return"
    \n"+e+"
    \n"},r.prototype.html=function(e){return e},r.prototype.heading=function(e,t,n){return"'+e+"\n"},r.prototype.hr=function(){return this.options.xhtml?"
    \n":"
    \n"},r.prototype.list=function(e,t){var n=t?"ol":"ul";return"<"+n+">\n"+e+"\n"},r.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},r.prototype.paragraph=function(e){return"

    "+e+"

    \n"},r.prototype.table=function(e,t){return"\n\n"+e+"\n\n"+t+"\n
    \n"},r.prototype.tablerow=function(e){return"\n"+e+"\n"},r.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' style="text-align:'+t.align+'">':"<"+n+">")+e+"\n"},r.prototype.strong=function(e){return""+e+""},r.prototype.em=function(e){return""+e+""},r.prototype.codespan=function(e){return""+e+""},r.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},r.prototype.del=function(e){return""+e+""},r.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent(a(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:"))return""}var i='
    "},r.prototype.image=function(e,t,n){var r=''+n+'":">"},r.prototype.text=function(e){return e},i.parse=function(e,t,n){return new i(t,n).parse(e)},i.prototype.parse=function(e){var t=this;this.inline=new n(e.links,this.options,this.renderer),this.tokens=e.reverse();for(var r="";this.next();)r+=t.tok();return r},i.prototype.next=function(){return this.token=this.tokens.pop()},i.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},i.prototype.parseText=function(){for(var e=this,t=this.token.text;"text"===this.peek().type;)t+="\n"+e.next().text;return this.inline.output(t)},i.prototype.tok=function(){var e=this;switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var t,n,r,i,o="",a="";for(r="",t=0;te.length)break e;if(!(b instanceof i)){c.lastIndex=0;var k=c.exec(b),w=1;if(!k&&d&&v!=o.length-1){if(c.lastIndex=y,!(k=c.exec(e)))break;for(var x=k.index+(p?k[1].length:0),_=k.index+k[0].length,S=v,C=y,L=o.length;S=C&&(++v,y=C);if(o[v]instanceof i||o[S-1].greedy)continue;w=S-v,b=e.slice(y,C),k.index-=y}if(k){p&&(f=k[1].length);var x=k.index+f,k=k[0].slice(f),_=x+k.length,E=b.slice(0,x),T=b.slice(_),$=[v,w];E&&$.push(E);var A=new i(s,h?r.tokenize(k,h):k,g,k,d);$.push(A),T&&$.push(T),Array.prototype.splice.apply(o,$)}}}}}return o},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var i,o=0;i=n[o++];)i(t)}}},i=r.Token=function(e,t,n,r,i){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length,this.greedy=!!i};if(i.stringify=function(e,t,n){if("string"==typeof e)return e;if("Array"===r.util.type(e))return e.map(function(n){return i.stringify(n,t,e)}).join("");var o={type:e.type,content:i.stringify(e.content,t,n),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:n};if("comment"==o.type&&(o.attributes.spellcheck="true"),e.alias){var a="Array"===r.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(o.classes,a)}r.hooks.run("wrap",o);var s=Object.keys(o.attributes).map(function(e){return e+'="'+(o.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+o.tag+' class="'+o.classes.join(" ")+'"'+(s?" "+s:"")+">"+o.content+""},!t.document)return t.addEventListener?(t.addEventListener("message",function(e){var n=JSON.parse(e.data),i=n.language,o=n.code,a=n.immediateClose;t.postMessage(r.highlight(o,r.languages[i],i)),a&&t.close()},!1),t.Prism):t.Prism;var o=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return o&&(r.filename=o.src,document.addEventListener&&!o.hasAttribute("data-manual")&&("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(r.highlightAll):window.setTimeout(r.highlightAll,16):document.addEventListener("DOMContentLoaded",r.highlightAll))),t.Prism}();e.exports&&(e.exports=n),void 0!==Te&&(Te.Prism=n),n.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},n.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),n.languages.xml=n.languages.markup,n.languages.html=n.languages.markup,n.languages.mathml=n.languages.markup,n.languages.svg=n.languages.markup,n.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},n.languages.css.atrule.inside.rest=n.util.clone(n.languages.css),n.languages.markup&&(n.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:n.languages.css,alias:"language-css"}}),n.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:n.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:n.languages.css}},alias:"language-css"}},n.languages.markup.tag)),n.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(true|false)\b/,function:/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/},n.languages.javascript=n.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,function:/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),n.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),n.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:n.languages.javascript}},string:/[\s\S]+/}}}),n.languages.markup&&n.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:n.languages.javascript,alias:"language-javascript"}}),n.languages.js=n.languages.javascript,function(){"undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector&&(self.Prism.fileHighlight=function(){var e={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"};Array.prototype.forEach&&Array.prototype.slice.call(document.querySelectorAll("pre[data-src]")).forEach(function(t){for(var r,i=t.getAttribute("data-src"),o=t,a=/\blang(?:uage)?-(?!\*)(\w+)\b/i;o&&!a.test(o.className);)o=o.parentNode;if(o&&(r=(t.className.match(a)||[,""])[1]),!r){var s=(i.match(/\.(\w+)$/)||[,""])[1];r=e[s]||s}var l=document.createElement("code");l.className="language-"+r,t.textContent="",l.textContent="Loading…",t.appendChild(l);var u=new XMLHttpRequest;u.open("GET",i,!0),u.onreadystatechange=function(){4==u.readyState&&(u.status<400&&u.responseText?(l.textContent=u.responseText,n.highlightElement(l)):u.status>=400?l.textContent="✖ Error "+u.status+" while fetching file: "+u.statusText:l.textContent="✖ Error: File does not exist or is empty")},u.send(null)})},document.addEventListener("DOMContentLoaded",self.Prism.fileHighlight))}()}),Pe={},Oe=/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,.\/:;<=>?@\[\]^`{|}~]/g;T.clear=function(){Pe={}};var je=decodeURIComponent,Me=encodeURIComponent,qe=e(function(e){return/(:|(\/{2}))/g.test(e)}),Ne=e(function(e){return/\/$/g.test(e)?e:(e=e.match(/(\S*\/)[^\/]+$/))?e[1]:""}),Re=e(function(e){return e.replace(/^\/+/,"/").replace(/([^:])\/{2,}/g,"$1/")}),Fe={},He=function(t,n){this.config=t,this.router=n,this.cacheTree={},this.toc=[],this.linkTarget=t.externalLinkTarget||"_blank",this.contentBase=n.getBasePath();var i,o=this._initRenderer(),a=t.markdown||{};r(a)?i=a($e,o):($e.setOptions(se(a,{renderer:se(o,a.renderer)})),i=$e),this.compile=e(function(e){var n="";return e?(n=i(e),n=t.noEmoji?n:A(n),T.clear(),n):e})};He.prototype.matchNotCompileLink=function(e){for(var t=this.config.noCompileLinks,n=0;n'+e+""},a.code=e.code=function(e,t){return void 0===t&&(t=""),'
    '+Ae.highlight(e,Ae.languages[t]||Ae.languages.markup)+"
    "},a.link=e.link=function(e,t,i){void 0===t&&(t="");var a="",s=M(t),l=s.str,u=s.config;return t=l,/:|(\/{2})/.test(e)||o.matchNotCompileLink(e)||u.ignore?a+=' target="'+n+'"':e=r.toURL(e,null,r.getCurrentPath()),u.target&&(a+=" target="+u.target),u.disabled&&(a+=" disabled",e="javascript:void(0)"),t&&(a+=' title="'+t+'"'),'"+i+""},a.paragraph=e.paragraph=function(e){return/^!>/.test(e)?k("tip",e):/^\?>/.test(e)?k("warn",e):"

    "+e+"

    "},a.image=e.image=function(e,t,n){var r=e,o="",a=M(t),s=a.str,l=a.config;return t=s,l["no-zoom"]&&(o+=" data-no-zoom"),t&&(o+=' title="'+t+'"'),qe(e)||(r=j(i,e)),''+n+'"};var s=/^\[([ x])\] +/;return a.listitem=e.listitem=function(e){var t=s.exec(e);return t&&(e=e.replace(s,'")),""+e+"\n"},e.origin=a,e},He.prototype.sidebar=function(e,t){var n=this.router.getCurrentPath(),r="";if(e)r=this.compile(e),r=r&&r.match(/]*>([\s\S]+)<\/ul>/g)[0];else{var i=this.cacheTree[n]||L(this.toc,t);r=b(i,"
      "),this.cacheTree[n]=i}return r},He.prototype.subSidebar=function(e){if(!e)return void(this.toc=[]);var t=this.router.getCurrentPath(),n=this,r=n.cacheTree,i=n.toc;i[0]&&i[0].ignoreAllSubs&&i.splice(0),i[0]&&1===i[0].level&&i.shift();for(var o=0;o')},He.prototype.article=function(e){return this.compile(e)},He.prototype.cover=function(e){var t=this.toc.slice(),n=this.compile(e);return this.toc=t.slice(),n};var ze=ge.title,Be=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};F(this,e),this.duration=t.duration||1e3,this.ease=t.easing||this._defaultEase,this.start=t.start,this.end=t.end,this.frame=null,this.next=null,this.isRunning=!1,this.events={},this.direction=this.startthis.end&&e>=this.next}[this.direction]}},{key:"_defaultEase",value:function(e,t,n,r){return(e/=r/2)<1?n/2*e*e+t:-n/2*(--e*(e-2)-1)+t}}]),e}(),De={},We=!1,Ue=null,Ye=!0,Ze=0,Ge=ge.scrollingElement||ge.documentElement,Xe={},Ve=function(e){this.config=e};Ve.prototype.getBasePath=function(){return this.config.basePath},Ve.prototype.getFile=function(e,t){e=e||this.getCurrentPath();var n=this,r=n.config,i=this.getBasePath();return e=r.alias?V(e,r.alias):e,e=J(e),e="/README.md"===e?r.homepage||e:e,e=qe(e)?e:j(i,e),t&&(e=e.replace(new RegExp("^"+i),"")),e},Ve.prototype.onchange=function(e){void 0===e&&(e=n),e()},Ve.prototype.getCurrentPath=function(){},Ve.prototype.normalize=function(){},Ve.prototype.parse=function(){},Ve.prototype.toURL=function(){};var Je=e(function(e){return e.replace("#","?id=")}),Qe=function(e){function t(t){e.call(this,t),this.mode="hash"}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.getBasePath=function(){var e=window.location.pathname||"",t=this.config.basePath;return/^(\/|https?:)/g.test(t)?t:Re(e+"/"+t)},t.prototype.getCurrentPath=function(){var e=location.href,t=e.indexOf("#");return-1===t?"":e.slice(t+1)},t.prototype.onchange=function(e){void 0===e&&(e=n),p("hashchange",e)},t.prototype.normalize=function(){var e=this.getCurrentPath();if(e=Je(e),"/"===e.charAt(0))return Q(e);Q("/"+e)},t.prototype.parse=function(e){void 0===e&&(e=location.href);var t="",n=e.indexOf("#");n>=0&&(e=e.slice(n+1));var r=e.indexOf("?");return r>=0&&(t=e.slice(r+1),e=e.slice(0,r)),{path:e,file:this.getFile(e,!0),query:P(t)}},t.prototype.toURL=function(e,t,n){var r=n&&"#"===e[0],i=this.parse(Je(e));if(i.query=se({},i.query,t),e=i.path+O(i.query),e=e.replace(/\.md(\?)|\.md$/,"$1"),r){var o=n.indexOf("?");e=(o>0?n.substr(0,o):n)+e}return Re("#/"+e)},t}(Ve),Ke=function(e){function t(t){e.call(this,t),this.mode="history"}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.getCurrentPath=function(){var e=this.getBasePath(),t=window.location.pathname;return e&&0===t.indexOf(e)&&(t=t.slice(e.length)),(t||"/")+window.location.search+window.location.hash},t.prototype.onchange=function(e){void 0===e&&(e=n),p("click",function(t){var n="A"===t.target.tagName?t.target:t.target.parentNode;if("A"===n.tagName&&!/_blank/.test(n.target)){t.preventDefault();var r=n.href;window.history.pushState({key:r},"",r),e()}}),p("popstate",e)},t.prototype.parse=function(e){void 0===e&&(e=location.href);var t="",n=e.indexOf("?");n>=0&&(t=e.slice(n+1),e=e.slice(0,n));var r=j(location.origin),i=e.indexOf(r);return i>-1&&(e=e.slice(i+r.length)),{path:e,file:this.getFile(e),query:P(t)}},t.prototype.toURL=function(e,t,n){var r=n&&"#"===e[0],i=this.parse(e);return i.query=se({},i.query,t),e=i.path+O(i.query),e=e.replace(/\.md(\?)|\.md$/,"$1"),r&&(e=n+e),Re("/"+e)},t}(Ve),et={},tt=Object.freeze({cached:e,hyphenate:ae,merge:se,isPrimitive:t,noop:n,isFn:r,inBrowser:be,isMobile:ke,supportsPushState:we,parseQuery:P,stringifyQuery:O,getPath:j,isAbsolutePath:qe,getParentPath:Ne,cleanPath:Re}),nt=oe.prototype;!function(e){e._init=function(){var e=this;e.config=le||{},i(e),ie(e),o(e,"init"),ee(e),X(e),te(e),re(e),o(e,"mounted")}}(nt),function(e){e.route={}}(nt),function(e){e._renderTo=function(e,t,n){var r=a(e);r&&(r[n?"outerHTML":"innerHTML"]=t)},e._renderSidebar=function(e){var t=this.config,n=t.maxLevel,r=t.subMaxLevel,i=t.loadSidebar;this._renderTo(".sidebar-nav",this.compiler.sidebar(e,n));var o=R(this.router,".sidebar-nav",!0,!0);i&&o?o.parentNode.innerHTML+=this.compiler.subSidebar(r)||"":this.compiler.subSidebar(),this._bindEventOnRendered(o)},e._bindEventOnRendered=function(e){var t=this.config,n=t.autoHeader,r=t.auto2top;if(I(this.router),n&&e){var i=a("#main"),o=i.children[0];if(o&&"H1"!==o.tagName){var s=u("h1");s.innerText=e.innerText,h(i,s)}}r&&W(r)},e._renderNav=function(e){e&&this._renderTo("nav",this.compiler.compile(e)),R(this.router,"nav")},e._renderMain=function(e,t){var n=this;if(void 0===t&&(t={}),!e)return Z.call(this,e);o(this,"beforeEach",e,function(e){var r=n.isHTML?e:n.compiler.compile(e);t.updatedAt&&(r=Y(r,t.updatedAt,n.config.formatUpdated)),o(n,"afterEach",r,function(e){return Z.call(n,e)})})},e._renderCover=function(e){var t=a(".cover");if(!e)return void f(t,"remove","show");f(t,"add","show");var n=this.coverIsHTML?e:this.compiler.cover(e),r=n.trim().match('

      ([^<]*?)

      $');if(r){if("color"===r[2])t.style.background=r[1]+(r[3]||"");else{var i=r[1];f(t,"add","has-mask"),qe(r[1])||(i=j(this.router.getBasePath(),r[1])),t.style.backgroundImage="url("+i+")",t.style.backgroundSize="cover",t.style.backgroundPosition="center center"}n=n.replace(r[0],"")}this._renderTo(".cover-main",n),N()},e._updateRender=function(){G(this)}}(nt),function(e){var t;e._fetch=function(e){var r=this;void 0===e&&(e=n);var i=this.route,o=i.path,a=i.query,s=O(a,["id"]),l=this.config,u=l.loadNavbar,c=l.loadSidebar;t&&t.abort&&t.abort(),t=_(this.router.getFile(o)+s,!0),this.isHTML=/\.html$/g.test(o);var h=function(){if(!c)return e();ne(o,s,c,function(t){r._renderSidebar(t),e()},r,!0)};t.then(function(e,t){r._renderMain(e,t),h()},function(e){r._renderMain(null),h()}),u&&ne(o,s,u,function(e){return r._renderNav(e)},this,!0)},e._fetchCover=function(){var e=this,t=this.config,n=t.coverpage,r=this.route.query,i=Ne(this.route.path),o=this.router.getFile(i+n);if("/"!==this.route.path||!n)return void this._renderCover();this.coverIsHTML=/\.html$/g.test(o),_(o+O(r,["id"])).then(function(t){return e._renderCover(t)})},e.$fetch=function(e){var t=this;void 0===e&&(e=n),this._fetchCover(),this._fetch(function(n){t.$resetEvents(),o(t,"doneEach"),e()})}}(nt),function(e){e.$resetEvents=function(){D(this.route.path,this.route.query.id),R(this.router,"nav")}}(nt),function(){window.Docsify={util:tt,dom:ye,get:_,slugify:T},window.DocsifyCompiler=He,window.marked=$e,window.Prism=Ae}(),oe.version="4.5.5",function(e){var t=document.readyState;if("complete"===t||"interactive"===t)return setTimeout(e,0);document.addEventListener("DOMContentLoaded",e)}(function(e){return new oe})}(); \ No newline at end of file diff --git a/_docs/editor.md b/_docs/editor.md new file mode 100644 index 0000000..2ef0960 --- /dev/null +++ b/_docs/editor.md @@ -0,0 +1,322 @@ +# 修改编辑器 + +?> 在这一节中,让我们来了解如何修改编辑器(包括配置表格和事件编辑器) + +在改动core时, 有时会相应的更改project中存储数据的结构, 而表格和事件编辑器不做相应更改的话就无法顺畅的编辑改动了结构的数据了, 此文档帮助造塔者进行`配置表格`的修改, 以及修改_server/MotaAction.g4和其他相关文件来调整事件编辑器的图块. + +## 修改表格 + +### demo - 给怪物增加 _速度_ 属性 + +点击素材区的怪物后点击配置表格, 或者直接打开_server/table/comment.js +拉到`【怪物】相关的表格配置`的部分 +``` js +// --------------------------- 【怪物】相关的表格配置 --------------------------- // +"enemys": { + "_type": "object", + "_data": { + "id": { + "_leaf": true, + "_type": "disable", + "_docs": "怪物ID", + "_data": "怪物ID,可于页面底部修改" + }, + "name": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "名称" + }, + "displayIdInBook": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_docs": "手册映射ID", + "_data": "在怪物手册中映射到的怪物ID。如果此项不为null,则在怪物手册中,将用目标ID来替换该怪物原本的ID。常被运用在同一个怪物的多朝向上。" + }, + "hp": { + "_leaf": true, + "_type": "textarea", + "_data": "生命值" + }, + "atk": { + "_leaf": true, + "_type": "textarea", + "_data": "攻击力" + }, +``` +可以看到, project/enemys.js 中怪物的属性 +```js +"greenSlime": {"name":"绿头怪","hp":100,"atk":120,"def":0,"money":1,"exp":1,"point":0,"special":[1,5,7,8]}, +``` +被逐条列出并解释 + +把hp的部分复制一份并修改 +```js +"speed": { + "_leaf": true, + "_type": "textarea", + "_data": "速度" +}, +``` +刷新之后, 怪物的表格在hp下面就多出了speed一项, 编辑后出现在了怪物属性中 +```js +"greenSlime": {"name":"绿头怪","hp":100,"atk":120,"def":0,"money":1,"exp":1,"point":0,"special":[1,5,7,8],"speed":123123}, +``` +### 表格配置项含义 + +可以看出表格配置中的对象是一层套一层的结构, `_`开头的和数据的名字间隔着展开, 忽略所有`_`的层级的话, 这个对象和project中的对象是有相同的结构 + +**_leaf** 为真的项对应的数据将作为表格的编辑的内容, 数字/字符串/空对象和数组会被自动计算为true, 非空的对象和数组会被计算为false, 手动填写_leaf的值可以强制不展开对象和数组来进行编辑 + +**_type** 决定了表格如何展示该数据 ++ select 使用下拉菜单展示 ++ checkbox 使用勾选框展示 ++ checkboxSet 使用勾选框组展示 ++ 其他 使用文本框展示, 根据类型区分, 双击或点编辑会有以下行为 + - textarea 文本编辑器(_type的默认值) + - event 事件编辑器 + - material 素材选取 + - color 取色器 + - point 地图选点 + - disable 不允许编辑 + - popCheckBoxSet 以弹窗形式多选框 + +当某个event例如 门信息编辑 无法适应修改后的数据结构, 可以修改事件编辑器, 也可以把_type改成textarea +以上类型的格式要如何写请搜索例如`"_type": "checkboxSet"`来查找例子, 此处不展示 + +**_range** 被编辑的值会作为`thiseval`来进行检测, 当字符串为真时才接收这个值, 同时当删除表格时会判定_range是否接收null + +**拓展性** + +注意这是js文件, 可以使用表达式, 如下例子引用了别处的数据作为下拉菜单 +```js +"bgm": { + "_leaf": true, + "_type": "select", + "_select": { + "values": [null].concat(Object.keys(editor.core.material.bgms)) + }, + "_docs": "背景音乐", + "_data": "到达该层后默认播放的BGM" +}, +``` + +同时`_`开头的项可以使用函数. 同一级中`_data`最先被计算. 复杂的结构的注释可以利用函数动态生成 +`_data`的参数`key`是各子项的名字, 其他`_`开头的参数是`args`详见editor_table.prototype.objToTable的注释 +例如: 自动事件数组, `"_leaf": false`强制展开, 通过`_action`函数即使为空也显示两个空白的项, 同时`_data`函数给自动事件的每一项标记为event +```js +"autoEvent": { + "_type": "object", + "_leaf": false, + "_action": function (args) { + args.vobj = args.vobj || {}; + for (var ii = 0; ii < 2; ii++) { + args.vobj[ii] = args.vobj[ii] || null; + } + }, + "_data": function (key) { + return { + "_leaf": true, + "_type": "event", + "_event": "autoEvent", + "_data": "自动事件" + } + } +}, +``` + +## 修改事件编辑器 + +_type为event的表格项, 在双击时会进入事件编辑器. 是由[antlr-blockly](https://github.com/zhaouv/antlr-blockly)生成的图块式的可视化编辑器 + +_event的内容例如autoEvent则是使用的入口图块的类型 + +请先查看[antlr-blockly的文档](https://zhaouv.github.io/antlr-blockly/docs/#/README)来了解.g4语法文件和可视化编辑器blockly以及库antlr-blockly的基础知识 + +### 事件编辑器的运行机制简介 + +事件编辑器编辑一个表格项的流程有以下几步 ++ 加载表格中的json, 将其根据类别转化成图块 ++ 通过拖拽填写图块编辑内容 ++ 将图块转化回json置入表格 + +其中图块到json 是由_server/MotaAction.g4完成的 +json到图块 是由_server/MotaActionParser.js完成的, 并依赖图块在g4中的定义 + +图块到json首先由入口方块出发, 入口方块又依次去获取了嵌入在其中的方块和域, 嵌入的方块再递归访问其嵌入的内容, 从而访问了所有方块上域中的信息. +例如 event_m 通过 action_0 获取了其中嵌入的所有语句生成的json, 插入到其自身的键'data'中 + +```antlr +//事件 事件编辑器入口之一 +event_m + : '事件' BGNL? Newline '覆盖触发器' Bool '启用' Bool '通行状态' B_0_List '显伤' Bool BGNL? Newline action+ BEND + + +/* event_m +tooltip : 编辑魔塔的事件 +helpUrl : https://h5mota.com/games/template/_docs/#/event +default : [false,null,null,null,null] +B_0_List_0=eval(B_0_List_0); +var code = { + 'trigger': Bool_0?'action':null, + 'enable': Bool_1, + 'noPass': B_0_List_0, + 'displayDamage': Bool_2, + 'data': 'data_asdfefw' +} +if (!Bool_0 && Bool_1 && (B_0_List_0===null) && Bool_2) code = 'data_asdfefw'; +code=JSON.stringify(code,null,2).split('"data_asdfefw"').join('[\n'+action_0+']\n'); +return code; +*/; +``` + +值得注意的是shoplist和action这样的语句集合, 语句集合会选择其中的最后一项作为父方块的默认块, 所以在希望其允许空时, 要制作一个空项放在语句集合定义的末尾, 例如 +```antlr +shoplist + : shopsub + | shopitem + | shopcommonevent + | emptyshop + ; + +emptyshop + : Newline + + +/* emptyshop +var code = ' \n'; +return code; +*/; +``` + +json到图块则是从ActionParser.prototype.parse出发, 递归的将js对象变为图块, 例如event类型的json, 使用event_m入口图块, 将obj.trigger/enable/noPass/displayDamage填到域中, obj.data交给this.parseList转化为语句的图块, 与g4中注入的语句做相反的事情 +```js +ActionParser.prototype.parse = function (obj,type) { + switch (type) { + case 'event': + if(!obj)obj={}; + if(typeof(obj)===typeof('')) obj={'data':[obj]}; + if(obj instanceof Array) obj={'data':obj}; + return MotaActionBlocks['event_m'].xmlText([ + obj.trigger==='action',obj.enable,obj.noPass,obj.displayDamage,this.parseList(obj.data) + ]); +``` + +### demo - 给已有图块添加新域 + +例如给'播放背景音乐'图块再额外加一个参数列表 + +不熟悉时, 对任意图块来说这都算是最简单粗暴的修改方式了, 复杂的修改需求也只需要改这一下, 只是填图块时没那么方便 + +编辑g4时推荐使用vscode, 搜索并安装插件mota-js + +
      playBgm_s
      +    :   '播放背景音乐' EvalString '开始播放秒数' Int '持续到下个本事件' Bool '参数列表' JsonEvalString? Newline
      +    
      +
      +/* playBgm_s
      +tooltip : playBgm: 播放背景音乐
      +helpUrl : https://h5mota.com/games/template/_docs/#/event?id=playbgm%EF%BC%9A%E6%92%AD%E6%94%BE%E8%83%8C%E6%99%AF%E9%9F%B3%E4%B9%90
      +default : ["bgm.mp3", 0, true]
      +allBgms : ['EvalString_0']
      +colour : this.soundColor
      +Int_0 = Int_0 ? (', "startTime": '+Int_0) : '';
      +Bool_0 = Bool_0 ? ', "keep": true' : '';
      +if (JsonEvalString_0) {
      +    JsonEvalString_0 = ', "args": ' +JsonEvalString_0;
      +}
      +var code = '{"type": "playBgm", "name": "'+EvalString_0+'"'+Int_0+Bool_0+JsonEvalString_0+'},\n';
      +return code;
      +*/;
      + +使用了类型JsonEvalString, 并在注入的代码中处理了其新增出的JsonEvalString_0要如何体现在这个方块产生的json +此处使用了antlr-blockly产生的'类型名_次数'的默认名称, 也可以开头加一行`name : [null,null,null,'args']`来把这个变量命名为args, 对于setText_s这种域比较多的块会很有帮助. +在MotaAction中, 除了默认的Int/Number等 +常规的字符串使用EvalString +ID类型的使用IdString +允许空的自然数使用IntString +位置坐标使用PosString +不转义的多行文本使用RawEvalString +json类型的文本使用JsonEvalString + +相应的也需要改json到图块的部分 + +
            break;
      +    case "playBgm":
      +      this.next = MotaActionBlocks['playBgm_s'].xmlText([
      +        data.name,data.startTime||0,data.keep||false,data.args,this.next]);
      +      break
      +    case "pauseBgm":
      + + +### demo - 增加新语句图块 + +假设已经制作了一个名为`陨石坠落`的公共事件, 其参数列表是长度3的数组, 依次是陨石颜色, 陨石位置, 爆炸范围. + +每次要使用插入公共事件并要填`陨石坠落`和形如`[[231,231,41],[5,7],1]`的长而且带有格式的内容, 并且无法使用取色器和地图选点功能. 此时就可以借助'注册一个自定义事件'并填加新语句图块来解决这个问题. + +首先注册成名为'meteorite'的事件, 在插件编写的init中添加以下内容 +```js +core.registerEvent('meteorite', function(data){ + core.insertAction({"type": "insert", "name": "陨石坠落", "args": [data.color, data.loc, data.range]}); + core.doAction(); +}) +``` + +然后在g4中修改以下位置(请善用搜索来定位) + +
          |   drawSelector_1_s
      +    |   unknown_s
      +    |   function_s
      +    |   meteorite_s
      +    |   pass_s
      +    ;
      +
      +text_0_s
      + +并在分号后面添加 + +```antlr +meteorite_s + : '陨石坠落' '颜色' ColorString? Colour 'x' PosString ',' 'y' PosString '爆炸范围' Int Newline + + +/* meteorite_s +tooltip : 陨石坠落 +helpUrl : https://h5mota.com/games/template/_docs/#/event +default : ["255,0,0,1",'rgba(255,0,0,1)',"0","0",1] +selectPoint : ["PosString_0", "PosString_1"] +colour : this.soundColor +var loc = ', "loc": ['+PosString_0+','+PosString_1+']'; +ColorString_0 = ColorString_0 ? (', "color": ['+ColorString_0+']') : ''; +var code = '{"type": "meteorite"'+ColorString_0+loc+', "range": '+Int_0+'},\n'; +return code; +*/; +``` + +希望是特效的颜色, 于是在g4搜索'画面闪烁', 找到了`colour : this.soundColor`, 抄出其中的颜色的写法, 地图选点的写法同理, 搜索'强制战斗'后模仿其写法 + +在MotaActionParser.js中添加json到图块的代码, 同理模仿'画面闪烁'和'强制战斗' + +
            break;
      +    case "playBgm":
      +      this.next = MotaActionBlocks['playBgm_s'].xmlText([
      +        data.name,data.startTime||0,data.keep||false,this.next]);
      +      break;
      +    case "meteorite":
      +      data.color = this.Colour(data.color)
      +      this.next = MotaActionBlocks['meteorite_s'].xmlText([
      +        data.color,'rgba('+data.color+')',data.loc[0],data.loc[1],data.range,this.next]);
      +      break;
      +    case "pauseBgm":
      + +最后在editor_blocklyconfig.js中将其加入到工具栏中 + +
          '特效/声音':[
      +      MotaActionBlocks['meteorite_s'].xmlText(),
      +      MotaActionBlocks['sleep_s'].xmlText(),
      + +========================================================================================== + +[继续阅读下一章:UI编辑器](ui-editor) \ No newline at end of file diff --git a/_docs/element.md b/_docs/element.md new file mode 100644 index 0000000..449223a --- /dev/null +++ b/_docs/element.md @@ -0,0 +1,467 @@ +# 元件说明 + +?> 在这个部分,将详细讲解编辑器的每个部件的用法。 + +## 素材区 + +素材区在展开状态下,从左到右分为若干列: + +1. `project\materials\terrains.png`:位于素材区第一列(最上面两个图块不在这张图片里,它们分别是擦除和空气墙),其中从楼梯开始往下有系统含义,请勿随意修改其图块属性。 +2. `project\materials\animates.png`:位于素材区第二列,共4帧。主要为星空、岩浆、三色墙、六色门、四向出入口箭头、四种路障。 +3. `project\materials\enemys.png`:32×32px(像素,下同)的怪物,您可以随意修改它们的任何属性。如果嫌两帧太少,还可以作为32×48px怪物画在靠下2/3部分。 +4. `project\materials\enemy48.png`:32×48px的怪物,自带只有四只。您可以随意修改它们的任何属性。 +5. `project\materials\npcs.png`:32×32px的NPC,如老人、商人、小偷、公主、仙子、木牌、魔龙和章鱼的其他8块,您可以随意修改它们的任何属性。 +6. `project\materials\npc48.png`:32×48的NPC,自带只有样板0层的小姐姐,但您也可以用它来制作32×48px的门。 +7. `project\autotiles`:自动元件,会随着在地图上的连续摆放而自动采取适当的绘制方式。 +8. `project\tilesets`:额外素材,用来突破其他素材合计不得超过10000个的限制。您可以在这个区域拖动来批量框选,再在地图区单击成片绘制或拖动平铺。 + +V2.7.3中,`terrains.png`追加了薄墙图块(红线),V2.8中它们被挪到了最下方,可供利用。 + +V2.8中,自动元件在素材区折叠模式下会只显示左上角一格,与RPG Maker一致。 + +## 地图编辑(快捷键Z) + +![image](img/editor.jpg) + +如图所示,您可以在此对地图进行清空或删除操作,也可以新建或批量新建任意宽高的空白地图。 + +其中“导出并复制地图”是指显示出左侧的矩阵并复制(一般用来跨塔复制地图),您也可以直接改动其中的数字(例如将绿色史莱姆批量替换为红色史莱姆),再点击“从框中导入地图”就能将改动的结果同步到地图上。 + +下面的“楼层ID、中文名、状态栏名”分别对应楼层属性(快捷键V)的floorId、title和name,其中floorId也作为文件名(不能使用中文,注意大小写问题),title会显示在楼传界面和楼层切换黑屏,name也允许使用中文但请注意控制字数。 + +## 图块属性(快捷键C) + +![image](img/mapsc.jpg) + +如上图,除怪物和道具外,所有素材的图块属性都定义在`project\maps.js`中。道具和怪物属性也支持清空和批量复制,下面逐一讲解各条目的含义和用法: + +1. **图块ID:**图块的唯一标识符`core.getBlockId(x, y, floorId, showDisable)`,不允许使用中文和纯数字。请注意,额外素材`tileset`的图块ID由素材图片的顺序和图块在图片上的位置确定,无法更改,也请勿随意调换图片的顺序。样板已注册的图块中建议只修改怪物和NPC的图块ID,修改方法为上图最下方的**修改图块id为**。 +2. **图块数字:**见前面的描述,额外素材的数字由ID去掉字母X得到。 +3. **图块类别:**图块素材的类型。 +4. **图块名称:**怪物在手册中、道具在道具栏中、其他图块在剧情对话中的默认名称,可以随意修改。但原则上不推荐不同的怪物和道具有重复的名称,否则会影响事件编辑器的中文替换功能。 + +V2.8中,图块名称在有罗马数字、希腊字母、日文假名的情况下也支持事件编辑器中的中文替换了,这大大方便了一些科幻或魔法题材的作品。 + +V2.8中,道具名称支持使用${表达式计算}语法,但这样做会使其在事件编辑器中的中文替换失效。 + +你可以随时使用 `core.getBlockId(x, y, floorId, showDisable)` 获得地图上任何一个点的图块ID;`core.getBlockCls(x, y, floorId, showDisable)` 或的地图上任何一个点的图块类别;详见[API列表](api)。 + +在讲解其他属性之前,这里简单介绍一下素材的注册机制: + +* 除自动元件和额外素材外,其余图块只有在注册后才拥有上面所说的ID和数字。 +* 未注册的图块则只有“索引”,索引为n表示该图块在图片的第(n+1)行, +* ID和索引的对应关系定义在`project\icons.js`中。 +* 尝试用未注册的图块(如利用便捷PS工具新追加的图块)在地图上绘制,就会出现红色的问号方框。 +* 此时请在数据区手动注册此图块,只需填写一个新ID和数字(10000以内)即可。 +* 也可以点击“自动注册”按钮批量注册该图片的所有未注册素材,自动注册出的ID无任何语义(一般是一个表示图块类别的大写字母加几个数字),建议手动修改成有语义的内容(如史莱姆以Slime结尾)。 + +自动元件的注册与此不同,除了替换样板现有的几个外,如果还需要追加新的,请在地图区下方的下拉框中切换到“追加素材”(快捷键M),然后导入文件到画板autotile,再点击“追加”按钮即可。非Windows系统追加其他素材也主要依靠这种方式,具体用法请自行探索。 + +V2.7起,电脑端支持将文件直接拖动到素材区对应的列,进行追加。具体规则如下: +1. 道具的图片宽高必须为32的倍数,每格会被追加为一个新道具 +2. 其他类别的多帧图块(不包括自动元件和tileset),规格必须为128×128、128×192、96×128、96×192之一,分别对应32×32和32×48的四行四列或四行三列(后者为RPG Maker VX Ace格式)。 +3. 向两帧的enemys.png或npcs.png追加时,四列的图片取中间两列,三列的图片取两边两列。 +4. 向四帧的animates.png、enemy48.png、npc48.png追加时,三列的图片按2123排布。 + +V2.8起,图块支持“(注销并)删除”操作,执行后将(对已注册的)删除该图块在maps.js和icons.js的信息(如果是怪物和道具则还会删除对应的信息),然后将该图块从图片上删除,图片上更靠下的图块会被统一上移一格。 + +### 非怪物非道具属性 + +1. **触发器:**当碰触到地图上此图块时触发的系统事件,详见[事件](event)。 + * **battle**: (未列出)战斗;当撞上一个怪物且没有覆盖触发器时(参见[事件](event))将自动调用此触发器产生战斗,并触发战前和战后事件。 + * **getItem**: (未列出)拾获道具;当撞上/轻拾一个道具且没有覆盖触发器时(参见[事件](event))将自动调用此触发器获得它,并触发(拾获)道具后事件。 + * **changeFloor**: (未列出)楼层切换;对于地图上绑定的绿点(常见于楼梯或彩色箭头)将自动调用此触发器产生楼层切换事件。 + * **openDoor**: 用于制作门效果,当撞上此图块时将尝试开门(仅对`animates`和`npc48`生效),并触发开门后事件;具体开门动画参见下面的门信息。 + * **pushBox**: 推箱子;请勿对非箱子使用此触发器。 + * **ski**: 滑冰;拥有此触发器的图块放置在背景层时,走上去将触发滑冰效果。 + * **custom**: 自定义系统触发器;你可以使用 `core.registerSystemEvent` 来自己定义一个系统触发器,参见[API列表](api)。 +2. **可通行性:**勾选后勇士才可以踏入此图块,否则只能撞击此图块。(怪物被锁定为不可通行,道具被锁定为可通行,如有需要可以修改点上的不可通行性) +3. **碰触脚本/碰触事件:**勇士踏入或撞击此图块时执行的脚本,该项会被eval,相当于一种自定义的触发器,您可以参考踩灯和四种路障去填写它。V2.8起,新增了“碰触事件”(会覆盖该点的普通事件),使用起来更方便也更安全。 +4. **不可出入方向:**对三个图层的图块都有效。不可出方向指的是勇士站在这种图块上不能向哪个方向走(包括撞击),不可入方向指的是勇士不能从哪个方向走向这种图块(包括撞击)。例如,不可入方向勾选了“上”则不能“从上方、向下”走向这个图块。请参考素材区第一列的四个灰色箭头(样板1层右下角也有)。 + * V2.8.1起,编辑器地图区下方提供了“通行度”勾选框,您可以随时勾选它来查看“不可出入方向”(包括图块的和点的)在地图上的实际效果。 +5. **可破震:**勾选后,此图块将成为破墙镐(pickaxe)和地震卷轴(earthquake)这两个道具的目标。 +6. **动画帧数:**您可以修改此帧数来让本来有4帧的图块只用前2或3帧循环播放,另外制作门时请务必将此帧数改为1,表示门在打开前静止在第1帧。 +7. **门信息:**只对`animates`和`npc48`有效,您可以点击“编辑按钮”来填写此图块作为门的开关耗时、开关音效以及需要哪些钥匙各多少把(可以填写任何消耗类道具,也可以选择某些道具只需持有一定数量而不消耗)。修改此信息后,您需要将上面的“动画帧数”改为1,并可能需要将“触发器”改为openDoor(不改的话将无法通过撞击来开门,但可以像三色墙一样用来制作暗墙)。 + * 该项在V2.7首次提供,只支持“同时需要多种钥匙各多少把、消耗其中哪几种”的“且运算”条件。 + * 如果您需要指定“或运算”条件(如优先消耗一种钥匙,没有再消耗另一种),或“消耗量”少于“需要持有量”,或者需要判断的条件不是道具而是其他条件(比如勇士状态或flag),请使用“碰触事件”,但这样将无法自动存档。 + * V2.8新增了(批量)开门后事件,它和(单点)开门后事件的执行先后顺序,由“脚本编辑——开门后脚本”指定,默认先执行单点的。 + * 因此,复杂的开门条件也可以换个角度思考,把开门条件设为什么都不需要(先斩后奏,这一思想在道具使用条件中也很常见),开门后事件中再进行判断,如果开门成功就按照条件扣除钥匙,开门失败就再把门关上就好了,这样就保留了自动存档功能。 + * V2.8.1起,npc48支持绑定大型贴图,您可以制作大型门了,开关门的动画会更为震撼! +8. **行走图朝向:**设置后,当勇士撞击该图块时,图块会尝试转身面向勇士(对话事件结束前请使用“事件转向”指令将其转回去)。走动时也会尝试自动转向,请参考样板0层使用的小姐姐。 + * V2.8起,“行走图朝向”可以用于怪物,绑定后只需手动设置脸朝下的怪物属性(包括下面的绑定贴图)而不用管其他的,手册显示、实际战斗、阻激夹域(尤其是夹击)、漏怪检测(`hasEnemyLeft`)等都会强制读取脸朝下的怪物,游戏中动态修改任何一个朝向的怪物属性都会立即强制同步到四个怪物身上。 + * npc在设置行走图朝向以后,其他属性不具有自同步机制,包括下面的绑定贴图,请自行注意。 + * 该项的四个方向甚至可以写不同的图块类别,后果未知。 +9. **绑定贴图:**V2.8.1新增,可以用于NPC和怪物。这很大程度上弥补了H5魔塔比起RPG Maker不能使用大型行走图的缺憾。 + * 点击此项时,将会直接弹窗请求选择文件,文件首先会判断总高度和总宽度的比例是否大于0.5。 + * 【1×4】如果这个比例不大于0.5(这个临界值可以通过复写`core.maps._getBigImageInfo`来修改),那么整张图视为1行4列(每列的高度至多为宽度的2倍),即一个图块的单向4帧行走图。方向默认视为朝下,如需修改,可以在其“行走图朝向”只绑定一个需要的方向(id写自己)。 + * 【4×4】如果这个比例大于0.5,那么整张图视为4行4列共16块,即4个朝向图块各自的4帧行走图(从上到下的朝向分别为“下、左、右、上”),因此您需要注册四个(最好是同类别的)图块并绑定好行走图朝向,贴图则都绑定这一张。 + * 该图块放在地图的事件层以后本体会透明化,并且根据朝向(默认向下)选择行走图的绘制偏移量。 + * 例如,朝下的图块,行走图在绘制时会让本体位于图片下缘中央,其他方向同理。 + * 而在编辑器中,1:1显示模式下大贴图会被缩小到1格,如需预览原始尺寸的效果,请点击地图区下方的“大地图”按钮。 + * 游戏中该图块会全程使用绑定的贴图(选择项的子选项图标和文本的`\\i[]`转义序列仍然使用本体)进行显示,包括在怪物手册和显示文章的`\t[]`效果(这两种都会被缩小到一定尺寸)。而显示文章的`\b[]`效果则还是根据图块类别使用原始高度,不过V2.8.1中您可以手动去掉尖角然后指定对话框的左上角坐标和限宽。 + * 对该图块执行“显隐事件、转变图块、开关门(npc48)、不透明度设置和渐变、移动跳跃”都会直接操作大贴图,这比起2.7.x的楼层贴图要好用许多! + * 但是,如果您有一张图片希望分成16小格,但是每行对应完全独立的四个图块而不具有行走图朝向关系,该怎么做呢? + * “插件编写”的第一项init中,提供了“资源加载后的操作”,您可以在里面用`splitImage`将该图的四行提前裁剪成四张图片并重新命名,就可以使用啦! + * 手动使用脚本切分出的新图片无法通过弹窗选取,必须手动填写,敬请谅解。 + * 如果希望能够弹窗选取,可以使用全塔属性中的“图片切分”功能。 +``` js + this._afterLoadResources = function () { + // 这是一个将4by4.png(假设为384*384)按行拆分成四个图片并保存的样例, + // 可以用来在没有条件ps(如原图尺寸不支持便捷ps或者手机造塔时)的条件下, + // 预处理图片来得到独立朝向的大型行走图(1of4.png到4of4.png)。 + var arr = core.splitImage('4by4.png', 384, 96); // 以宽386高96进行切分图片 + for (var i = 1; i <= arr.length; ++i) + core.material.images.images[i + 'of4.png'] = arr[i - 1]; + } +``` +### 道具属性 + +样板自带的道具都在样板0层摆好了,您可以直接进入游戏捡起它们,就会看到该道具的注意事项,这里不再赘述。 + +1. **道具类别:**虽然和图块类别的英文缩写都是cls,但有本质区别,请注意区分。道具的图块类别都是items,而道具类别分为以下几种: + * items:是的你没看错,又是`items`这个词,请注意和图块类别的`items`相区分。它表示即捡即用类不进背包的道具,如四种血瓶、三种宝石等。这类道具需要用到的其他属性有“即捡即用效果”、“即捡即用提示”、“碰触或使用事件”。 + * tools:进背包的消耗类道具,如钥匙和解药瓶、便携式血瓶蓝瓶(生命魔杖)、破震炸飞和跳跃靴等。这类道具需要用到的其他属性有“道具描述”、“不显示在道具栏”、“回放不绘制道具栏”、“碰触或使用事件”、“使用效果”、“能否使用或装备”。 + * constants:进背包的永久道具(每件在背包的数量要么为1要么为0),如手册、楼传、幸运金币、十字架、护符、二倍斩等,这类道具需要用到的其他属性和tools一致。 + * equips:装备,它需要用到的其他属性有“道具描述”、“道具的装备属性”、“能否使用或装备”。 + * 例如:如果您想把四种血瓶和三种宝石改为便携式,只需把其道具类别改为tools(当然,楼层属性中的ratio一项也就失效了)。 + * 如果想把大黄门钥匙变为钥匙盒(红黄蓝钥匙各一把),只需把其道具类别从tools改为items + * 如果想把剑盾变成装备,只需把其道具类别改为`equips`; + * 如果想修改破墙镐/炸弹/冰冻徽章的目标个数(V2.8支持改为八方向,使用`core.utils.scan2`即可)或让炸弹能够获得金经/触发战后事件,请修改它们的使用效果。 +2. **道具描述:**对除即捡即用类外的道具都有效。一个字符串,为道具在道具栏里的描述,也作为首次捡到时的提示信息的一部分(如果全塔属性中开启了这一提示功能的话)。支持使用`${表达式计算}`语法(如四种血瓶和三种宝石那样,但不支持中文替换),此语法的详细规则见“显示文章正文的转义序列”,和“值块和冒号缩写量”。 +3. **不显示在道具栏:**对tools和constants有效,勾选此项后,该道具在背包中将不显示出来。常用于不能主动使用或已有专门的使用按钮的道具来节省显示篇幅,如手册、楼传、幸运金币、十字架、护符和钥匙等。该属性的详细原理,见“脚本编辑(N键)”最下面的“道具栏显示项”。 +4. **回放不绘制道具栏:**勾选此项后,录像回放中使用此道具将不显示黑黑的道具栏。常用于频繁使用的道具,如楼传、技能和冰冻徽章等。 +5. **即捡即用效果:**如题,该项会被eval,一般为一行下述的代码: `core.status.hero.xxx += yyy * core.status.thisMap.ratio` + * 其中xxx为勇士的某种状态(如生命hp、生命上限hpmax、魔力mana、魔力上限manamax、护盾mdef、攻防、金经) + * yyy为此道具的基础效果(如四种血瓶和三种宝石的基础效果定义在了全塔属性中) + * `core.status.thisMap.ratio`则是指该道具所在楼层的“楼层属性”最下面的“宝石血瓶效果”。 + * 此效果会在“B键数据统计”被模拟执行(使用道具所在楼层的ratio)然后计算执行前后勇士状态差异,因此请尽量只在里面用脚本直接操作`core.status.hero`,而不要使用tip或特效。 + * 可以在此项中使用`core.insertAction([...])`或`core.insertCommonEvent()`插入事件或公共事件,但不会计入数据统计。 +6. **即捡即用提示:**实际显示时会被接在“获得xxx”后面,所以该项总是一个以逗号开头的字符串,同样支持`${表达式计算}`语法。 +7. **碰触或使用事件:**对除equips外都有效。该项用于代替“即捡即用效果”(但会使勇士停下脚步,且会晚于地图上的afterGetItem事件被执行,且不计入B键数据统计)和“使用效果”,如样板中的黄宝石和生命魔杖。如果您的js语法基础薄弱,那么它将是您的不二之选。 +8. **使用效果:**对tools和constants有效。该项会被eval,一般为一个js函数,较为简单的使用效果(如解药瓶)也可能是一行代码,破炸冰的目标个数请直接在该项中修改。总的来说因为事件比起脚本更容易实现异步特效且录像安全性更好,所以如非必要,不建议用此项。 +9. **能否使用或装备:**对tools、constants、equips有效。该项也会被eval,一般为一个js函数,较为简单的使用条件(如解药瓶)也可能是一行形如`"core.hasFlag('xxx')"`的代码。 + * 如果该道具在任何情况下都不能主动使用,请留`null`(并最好勾选“不显示在道具栏”以节约显示篇幅)。如果该道具在任何情况下都可以主动使用,请填`"true"`。 + * 如果使用条件较为复杂,也推荐直接填`"true"`先斩后奏,在使用效果中再行判定,并在使用失败的场合使用`core.addItem('xxx')`静默返还一件该道具,如样板中的破墙镐和炸弹。 + * 如果用于装备,那么`null`表示任何情况下都可以装备。但是请注意装上以后,即使条件变得不满足也不会自动脱下。 +10. **道具的装备属性:**在介绍此项之前,请先留意一下“全塔属性”中的“装备孔”一项。该项为一个字符串数组,最多允许6项(13×13样板)或8项(15×15样板)。每一项为装备的类型名称,建议是两个汉字(如“武器”、“防具”)。类型允许重复,如可以让勇士最多同时装备两块盾牌。 + * 装备类型:一个自然数,和前面的“装备孔”对应,如0表示武器,1表示防具。如果装备孔有重复的名称则这里也直接写名称(不用加引号),穿戴时会自动尝试寻找第一个同类型的空闲装备位(如果同类型的装备位唯一则会不管空闲与否直接替换),没有空闲的话会提示玩家先卸下一件。 + * 普攻动画:`project\animates`文件夹中任何一个文件的名称(不带后缀,但需要在全塔属性中注册过,支持别名),只对第一个装备孔有效。普攻动画会播放在和勇士战斗的怪物位置处,如果是强制战斗的天降怪物,则会播放在勇士身上并跟随,请自行注意。详见“文件注册”使用动画。 + * 数值提升项:若干个键值对,表示该装备增加属性的常数值(支持负数)。7个常用属性可以通过下拉框选取,自定义的新属性也可以手动输入。 + * 百分比提升项:若干个键值对,表示该装备增加属性的百分比(支持负数,如填-10就表示减少10%),修改方法同上。 + * 请注意,两种提升项只能写常数,请不要认为写个flag变量就能自动变化了。 + * V2.8中,两种提升项可以在游戏过程中通过事件指令来修改(包括已经穿在身上的装备,但是修改后的值仍然视为常数),同时每件装备新增了“穿脱时事件”属性,该事件以自动事件方式实现,在换好装备后关闭装备栏的瞬间触发。而且下述各种buff可以使用冒号缩写量来读写了,详见[事件](event)。 + * “穿脱时事件”指的是身上某件装备“从无到有”和“从有到无”,不包括“从多到少”和“从少到多”(在装备孔有重复名称的情况下)。 + * “穿脱时事件”的自动事件定义在`libs\core.js`,如需修改(例如改为检测“多少”而不是“有无”),请在插件“init”中直接修改`core.initStatus.autoEvents`。 + * 这是一个一维数组,数组的每项为一个自动事件对象,对象的`symbol`属性含有`"equipEvent_"`时就表明这是一个穿脱时事件。您可以修改其触发条件以及执行内容的第一项(flag变化)。 + * 装备对属性的影响原理:在穿脱装备时,会根据数值提升项和百分比提升项,分别调用`core.status.hero.xxx += yyy`和`core.addBuff('xxx', yyy)`这两个API(衰弱的附加和解除同理),而状态栏的显示值和战斗中的使用值则是`core.getStatus('xxx')`和buff值相乘再向下取整所得。 + * 可以按Alt+0~9快速更换套装(在装备界面按这个组合键则是保存套装),手机端的Alt键在V2.8被提供为粘滞键,点击状态栏右下角的难度标签就能看到。 + +道具相关API请阅读[API列表](api)。 + +### 怪物属性 + +1. **手册ID:**【已弃用】,原本是V2.8以前用来不完全实现多朝向怪物和大型怪物缩略图的手段,现在建议全部用新增的“行走图朝向”属性和“单点图块不透明度设置”代替。 + * 该项设置后怪物将在手册中不显示为原怪物而显示为对应的怪物ID(比“行走图朝向”更优先),如果对应ID的怪物在本楼层恰好也存在,那么看上去就像原怪物在手册中消失了(即RMXP魔塔的“伪装”属性),可以适当利用这一点。 + * 漏怪检测(`hasEnemyLeft`)和阻激夹域(尤其是夹击)依然只会使用原怪物ID,如有需求,请使用“行走图朝向”属性。 +2. **生命、攻防、金经:**如题,注意金经必须在“全塔属性”(快捷键B)中的“状态栏显示项”中也勾选才能真正被启用。持有幸运金币时打怪获得的金币翻倍,附加诅咒状态时打怪不获得金经。 +3. **加点:**若全塔属性勾选了“加点”,则此项为正数时将作为与该怪物每场战斗胜利后传递给“公共事件——加点事件”的参数(即那里的`flag:arg1`,默认表示加点的倍率),您可以自行修改该事件。 +4. **不可炸:**勾选后该怪物不会成为炸弹的目标,有阻击怪在场的情况下请务必给有(单点)战后事件的怪物(如机关门守卫和boss)勾选此项,否则玩家可能会偷梁换柱地炸掉该怪物并把阻击怪推过去打死来触发战后事件。不过V2.8.1起,炸弹的使用效果中提供了炸单点怪物触发战后事件的示例,可供利用。 +5. **(批量)战前/战后事件:**V2.8新增,用于解决阻击怪等会移动的怪物不能使用地图上的战前/战后事件的问题。 + * 两种战前事件都会在“撞击怪物、判定可以战胜、自动存档”后触发,这比起曾经的“覆盖触发器+天降强制战斗”对玩家更为友好。 + * 批量战前事件默认晚于单点战前事件(修改需要复写函数),批量战后事件与单点战后事件的执行顺序可以直接在“脚本编辑——战后脚本”中调整,默认也是单点先执行。 + * 两种战前事件在强制战斗时都不会触发,并且都早于支援怪的跳跃动画,因此捕捉怪的战前事件也不会触发。 + * 两种战前事件中如果使用“立刻结束当前事件”就会取消战斗,必要时您可以利用这一点制作回合制战斗。 + * 批量战前事件可以方便地制作“怪物先打一下勇士,再被勇士打死”的效果,同时延迟了怪物的消失,使得怪物根据“行走图朝向”转身后的效果能够来得及被看到。 + * 批量战后事件可以方便地制作“被打败后立即变身为另一种怪物/路障,或掉落一种道具”的效果。 + * 您新增加的类似先攻、破甲、净化、吸血、仇恨、固伤的属性可以使用战前/战后事件更便捷地实现并无视护盾,同时也避免了下面提到的各项value冲突的问题,但这样做的后果是不计入显伤,详见下面的解释。如果一定要这样实现,建议通过额外的说明提醒玩家。 +6. **特殊属性:**一个由正整数组成的一维数组,您可以点击“多选框编辑”按钮来修改它。所有特殊属性都定义在“脚本编辑——怪物特殊属性”,您可以在那里追加新的。它们大体分为四类: + 1. 手册中属性值的修正:(按照结算顺序)模仿、坚固、光环,修正后的属性也将被用于战斗,详见“脚本编辑——怪物真实属性”。 + 2. 战损的修正:这类最多,先攻、魔攻、连击(次数为n)、破甲(比例为defValue)、反击(比例为atkValue,回合数为勇士的攻击回合数)、净化(倍数为n,1表示单纯无视护盾)、吸血(比例为value,是否加到自身为add)、仇恨(每场战斗的仇恨增值由全塔属性指定)、无敌、固伤(数值为damage)、支援。其中又以仇恨和固伤不能被护盾直接抵消而和无敌较为特殊,详见“脚本编辑——战斗伤害信息”。 + 3. 战后的影响:中毒、衰弱、诅咒、仇恨(的累加和减半)、自爆、退化(扣减值分别为atkValue和defValue)、重生,详见“脚本编辑——战后脚本”和“脚本编辑——毒衰咒处理”。由于上面的“批量战前/战后事件”的存在,这种技能不再推荐由脚本实现。 + 4. 阻激夹域捕捉:即对主角行走的妨害,详见“脚本编辑——阻激夹域伤害”,该函数也负责了血网(图块ID为lavaNet,请勿修改)的伤害。 + +您会发现一个可怕的事情,那就是上述1和2会影响显伤,而3(例如自爆)和战前/战后事件不会(且后者对勇士生命的影响默认是无视护盾的)。这对于魔塔来说可能是致命的,毕竟没有玩家想看到“一个明明显伤不是红色的怪物打了却会死”等情况。当然,也有一种办法是像flash版新新魔塔1和2那样“战斗过程带有随机性或QTE,手册只显示预估伤害”,这种设计模式在rm和h5魔塔界并不常用,请谨慎尝试。 + +支援怪在进行支援时(护盾默认只计算一次)不具有坐标(类似天降强制战斗),因此不支持V2.8的“定点设置怪物属性”,同时基于坐标的一些判定也会失效。 + +发生支援时,金经(以及加点塔的加点值)都会累加,但加点事件只执行一次,也就是这次所有点数必须加在同一项属性,如需修改,请修改公共事件。 + +阻激域的伤害都为value且在夹击之前结算,领域的形状和半径与光环一致。如果需要更复杂的形状(如米字形激光),请自行研究该函数。 + +V2.8起,阻击和捕捉支持九宫格形状,由zoneSquare属性指定。如需实现九宫格夹击,请仿照它们。阻击默认可以推到已隐藏的事件处,如需禁止,请修改所在的函数。 + +您甚至可以给同一种怪物设置阻击和捕捉,那么它会先后退然后在原位置留下一个残影和勇士战斗,从而起到刷金经的作用。 + +样板的光环是作用于怪物的,如果想制作作用于勇士的光环,需要注意缓存问题以免卡顿。 + +可以看到,怪物属性中有很多值是彼此互相冲突的。请自行注意,比如设计新属性时分散给各项而不要都吊死在三个value上。最后介绍一些和怪物相关的API: +``` js + core.status.hero.flags.no_repulse = true; // 禁用阻击,包括伤害和后退效果 + core.status.hero.flags.no_laser = true; // 禁用激光 + core.status.hero.flags.no_betweenAttack = true; // 禁用夹击 + core.status.hero.flags.no_zone = true; // 禁用领域 + core.status.hero.flags.no_ambush = true; // 禁用捕捉 + core.getItem('amulet'); // 禁用血网等路障 + core.setEnemy('greenSlime', 'atk', 100); // 设置怪物属性,并计入存档 + core.getDamageString(enemy, x, y, floorId); // 获取某只怪的地图显伤字符串和颜色 + core.getCurrentEnemys(floorId); // 获取某层楼的(映射后)怪物列表,按战损递增排列 + core.hasEnemyLeft(enemyId, floorId); // 漏怪检测,两个参数都允许使用一维数组 + core.hasSpecial(special, test); // 检测special是否有test这一个特殊属性 +``` + +如果您想在数据区的表格中追加新的属性项,或修改已有项的格式、范围和长短注释,请点击数据区顶部的“配置表格”按钮,并参照已有的项去追加和修改,具体可查阅[修改编辑器](editor)。 + +至此您会发现,每种怪物不管出现在地图上的什么地方,其属性都是一样的(除非受到局部光环的影响)。所幸的是,V2.8提供了读写/移动单点怪物属性的API和事件,这种属性会在“脚本编辑——怪物真实属性”中最早生效,战后脚本中重置。此外,因受此属性或光环影响而导致同层多个同种怪物在手册中的数值不一致时,玩家可以选择作为多种怪物分别显示(包括具体坐标),详见游戏中的Esc菜单。 + +## 楼层属性(快捷键V) + +![image](img/floor.jpg) + +V2.7.1起,楼层支持使用最大128×128的超大地图,您可以在地图区下方点击“大地图”按钮切换到全景进行绘制,或在1:1模式下使用wsad进行视角移动。 + +V2.8.1新增了怪物的大贴图绑定,编辑器1:1模式下大贴图会被缩小到一格中,您可以点击“大地图”按钮让大贴图按原始尺寸绘制。 + +1. **楼层ID:**`project/floors`中的文件名,不允许使用中文也不能直接修改。修改方法见上图底部,修改后必须立即刷新浏览器页面,该项需要注意大小写问题。另外,作品发布时会统计楼层总数,如果有一些剧情层或连载塔半成品层不希望被统计进去,请使用“sample”+纯数字楼层ID即可,就像三个样板层一样。 +2. **楼层名:**楼层在楼传、上下楼黑屏和浏览地图界面的名称。 +3. **状态栏显示:**勇士在此楼层时状态栏左上角“上楼梯”图标右边的文字,允许使用中文,但请注意控制字数。 +4. **地图宽度和高度:**可在表格最下方修改。 + * 如果地图被加宽或加高,则“偏移”表示右移或下移的格子数(左边缘或上边缘用空格子填补)。 + * 如果地图被减窄或减矮,则“偏移”表示左移或上移的格子数(被移出左边缘或上边缘的图块将丢失)。 + * 在大地图中,您可能需要对地图中的绝对坐标和视野中的相对坐标进行互相转换。这需要用到`core.bigmap`的以下几个属性: + * `core.bigmap.width`和`core.bigmap.height`表示大地图的宽高(单位为格子)。 + * `core.bigmap.offsetX`和`core.bigmap.offsetY`表示视野左上角在大地图中的绝对像素坐标,再除以32就是格子坐标。 + * 因此,对于任意绝对坐标(x,y),它在视野中的相对坐标就是(x-offsetX/32,y-offsetY/32)。 + * 在大地图的非边缘区域,`hero.loc.x-core.bigmap.offsetX/32`和`hero.loc.y-core.bigmap.offsetY/32`总是都为6或7(样板尺寸的一半)。 + * 反之,已知相对坐标求绝对坐标就用加法,请举一反三。 +5. **几个勾选框:** + * 可楼传飞到:如果不勾选,则此楼层禁止成为楼传的目标楼层。该项的具体原理见“脚本编辑——flyTo楼层飞行”。 + * 可楼传飞出:如果不勾选,则勇士在此楼层禁止使用楼传。 + * 快捷商店:如果不勾选,则勇士在此楼层禁止快捷使用全局商店。事件中的“启用全局商店同时打开”不受影响,详见“插件编写——全局商店”。 + * 不可浏览:如果勾选,则此楼层无法通过滚轮/PageUp/PageDown键浏览,也不会计入全塔B键数据统计。 + * 不可瞬移:如果勾选,则勇士在此楼层无法用E键和点击瞬移,常用于用自动事件去监听勇士坐标时,或者需要严格的计步属性时(如每走10步失去一把黄钥匙)。 + * 是否是地下层:如果勾选,则楼传非平面模式下(或平面模式下该层还没有锚点时)勇士在此楼层原地使用楼传会传送到上楼点,详见“脚本编辑——楼层飞行”。 +6. **首次到达事件、每次到达事件:**如题,详见“脚本编辑——切换楼层后”。每层的这两个事件是共用独立开关的,常见用法有“进入新区时清空已到达的楼层列表,只保留当前楼层”从而实现勇士不能再飞回从前区域的效果。 +7. **并行处理脚本:**一个字符串,为勇士在此楼层时浏览器每帧都会执行一次(eval)的脚本,最快每秒60次。一般用来制作一些定时特效如bgs、bgv,详见“脚本编辑——并行脚本”。 +8. **上下楼点:**两个自然数构成的一维数组,将作为“楼层转换”事件(在地图上以图块左下角出现绿色小方块作为标记)和“楼层切换”指令中“上下楼梯”以及非平面楼传的目标坐标。 + * 如果不设置,则在传送时会尝试从地图中搜索上下楼梯图块。因此当某个楼层没有楼梯或有多个楼梯时(如《[新新魔塔](http://h5mota.com/games/xinxin/editor.html)》),请务必设置这个属性。点击“编辑”按钮从地图选点即可。 +9. **楼传落点:**格式和设置方法同上。如果设置了此项,则楼传在此层的落点将强制变为该点,无视平面模式下的离开点和上面的上下楼点以及该层实际的楼梯位置。 +10. **地面图块:**可以填写任何一个图块ID(需要加引号)或数字(如1是墙6是冰块),此项也会作为手册和剧情对话中的帧动画背景。填写tileset时可直接填写数字,不需要带字母X和引号。 +11. **色调:**一行四列的数组,前三项为小于256的自然数(分别表示红、绿、蓝),最后一项为0到1的浮点数表示不透明度,可以点击“编辑”按钮用调色器调色。 + * 值得一提的是,很多事件也以颜色作为参数,这些都是可以使用调色器调色的。 +12. **天气:**一行两列的数组,第一项为字符串“rain”、“snow”、“fog”、“cloud”、“sun”,第二项为不大于10的正整数,分别表示1—10级的雨天(雨丝数量和等级正相关)、雪天(由大小不一的白色实心圆组成,详见样板1层,雪花的数量和横飘速度和等级正相关)、雾天、多云、晴天(左上角洒下金色的阳光)。 + * 色调层在天气层上方、UI层下方(如不透明色调会遮盖天气,浏览地图看不到色调),关于图层的详细说明,参见“个性化” +13. **背景音乐:**如题,当在游戏中触发楼层切换时(包括读档),如果`flag:__color__、flag:__weather__、flag:__bgm__`没有值,游戏当时的画面色调、天气、背景音乐就会变为楼层属性中的这三个设置项,详见“脚本编辑——切换楼层中”。 + * V2.8起,该项允许多选,会随机播放其中一个。 +14. **宝石血瓶效果:**如题,必须填写且必须为正数。此项的用法为`core.status.thisMap.ratio`,请参考四种血瓶和三种宝石的捡拾效果。 + * V2.8起,手册中的“1防”改为“加防”,表示加ratio点防御能减伤多少。 + * 您还可以将其用于其他各种场合作为系数,如血网的伤害、中毒后每步的损血等。 +15. **楼层贴图:**【V2.8.1起】,怪物和npc可以直接绑定4帧大贴图了,因此楼层贴图主要用于布景。 + * 由于样板提供的图块只有32×32px和32×48px两种尺寸,且后者只能画在事件层,每个图块最多只能有4帧,因此对于一些多帧大图块十分不便,如npcs.png中被大卸八块的魔龙和章鱼。 + * 你可以使用“楼层贴图”,该项允许您使用任何尺寸、任何帧数的素材,唯一的缺点是不支持移动跳跃和淡入淡出效果。 + * 点击“编辑”按钮进入事件编辑器,每张图片的写法为(可从入口方块拖出,然后双击预览第一帧的效果): + 1. 图片名(name):如题,图片需要放在`project/images`文件夹并注册。 + 2. 翻转(:x/:y/:o):您可以对贴图的每帧进行三种翻转,当然,帧顺序在原图中依然是从左到右的。 + 3. 图层(bg/fg/auto):此项决定贴图绘制在哪个图层,您可以全部画在背景层或前景层。也可以选择“自适配”让贴图的上半部分画在前景层,下半部分画在背景层,比如树木等。如果选择了自适配,最好让下面的绘制坐标和宽高都是32的倍数。 + 4. 绘制坐标(x,y):贴图在地图中的左上角像素坐标,譬如x和y都填32则表示贴图左上角和“地图左上角格子的右下角”重合。 + 5. 初始禁用(Y/N):如果勾选了此项,则此贴图初始时不显示,您可以在事件中再将其显示出来。 + 6. 裁剪起点坐标(x,y)和宽高(w,h):此项规定了贴图在按帧切分前从原图中取哪一部分,x和y为所取部分在原图中的左上角坐标(不填视为两个0),w和h为所取部分的宽高(不填表示一直取到右下角)。 + 7. 帧数(frame):不填视为1,如果填写了大于1的整数,就会把上述裁剪得到的结果再从左到右等分为若干份,并在实际绘制时从左到右逐帧(可能还带有翻转)循环绘制,每帧的持续时间和其他图块一致。 + * 贴图本身只具有观赏性,您仍然需要使用空气墙等手段去控制其绘制区域各个点的通行性。 + * 在使用贴图来表现魔龙和章鱼这类大型怪物时,需要预先准备一种32×32(2帧)或32×48(4帧)的行走图,并注册为怪物,放在地图上时指定该点的不透明度为0,最后在该点的战后事件中隐藏贴图即可。 + * 你可以在插件中复写`drawBg`和`drawFg`函数以控制贴图和图块的绘制顺序(默认先绘制图块),详见[脚本](script)。 + ``` js + ////// 绘制背景层 ////// + core.maps.drawBg = function (floorId, ctx) { + var onMap = ctx == null; + if (onMap) { + ctx = core.canvas.bg; + core.clearMap(ctx); + } + this._drawBg_drawBackground(floorId, ctx); + // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块 + // 后绘制的覆盖先绘制的。 + this._drawFloorImages(floorId, ctx, 'bg'); + this._drawBgFgMap(floorId, ctx, 'bg', onMap); + } + ``` + +## 全塔属性(快捷键B) + +全塔属性共分为四部分:文件注册、初始勇士、全局数值、系统开关,您可以随时折叠其中任何一个部分。 + +![image](img/firstdatab.jpg) + +### 文件注册 + +这部分基本上都是经由多选框半自动完成的,下面逐一讲解: +1. **楼层列表:**`project/floors`文件夹中的文件名(不含后缀,但请务必注意大小写问题),此数组的顺序决定了楼传和上下楼器(fly、upFly、downFly)的顺序。 + * 如果您不慎将勇士的出生楼层注销了或不慎删除了某些楼层的js文件,导致编辑器页面打开后一片白屏,请手动打开`project/data.js`去小心地修改floorIds以和实际的文件名相匹配,并将出生楼层改为一个存在的楼层。 + * 其他更复杂的白屏请在控制台根据报错信息(安卓手机则使用ES文件浏览器查看日志)去小心地修改文件(如某个楼层文件有问题则可以注销它),如果难以独立解决,欢迎加QQ群959329661寻求帮助。 +2. **分区指定:**一行两列的数组,可以使用事件编辑器编辑,每行表示塔的一个区域,该行的两列分别表示该区域的第一层和最后一层(后者不填表示到塔顶),不在任何区域的楼层组成公区。 + * 游戏中,除了公区和勇士当前所在区以外的地图都将处于冻结状态,无法被浏览、无法被飞到、无法触发其自动事件、地图中的图块无法被读写。冻结状态的地图不会存入存档,从而节约了存档大小并加快了存读档速度,上百层的高层塔必备! +3. **使用图片、图片切分:**`project/images`文件夹中的文件名(需要后缀,必须全英数),单击“编辑”按钮,编辑器会自动扫描文件系统中格式合适的图片(如jpg、png和gif)。 + * 您可以预览并将需要的图片勾选。请注意,勇士的初始行走图必须在这里注册。另外,`winskin.png`只许替换为相同规格的图片而不要注销,否则道具商店等插件无法正常绘制。 + * V2.8.1起,新增了“图片切分”功能,用于代替插件init中的“资源加载后操作”函数。 + * 该项最核心的用法就是将4行4列共16块的大型行走图切成4行,绑定给没有朝向关系的独立图块。 + * 具体用法是,指定一张宽高分别为mw和nh的图片(限png格式),按照w和h的单位宽高裁剪,就会得到mn个小图。 + * 每个小图会被重命名为您指定的前缀+数字(从0起),按正常的Z字形文本顺序逐行从左到右编号。 +4. **额外素材:**`project/tilesets`中的文件名(需要后缀,只支持png)。 + * 注册方法同上,最大的区别在于这个数组的顺序必须保持好。如果随意调换其中的顺序,或注销不在数组末尾的图片,就会导致地图上最终呈现的素材发生错位。因此,新勾选的图片总会被自动追加到数组的末尾。 + * 比起常规素材,额外素材最大的好处有几点: + 1. 图片没有数量限制。常规素材的总数量最多只允许不到10000个,而额外素材每张图片上的图块数量最多允许3000个。 + 2. 查看和绘制更为方便。常规素材每个图块独占一行,每列为一帧,导致不方便观察,且用多个图块拼图必须逐图块绘制。额外素材都是静止的,所以每个图块只占一格,多个图块可以在准备素材时就直接以相邻的样子绘制在同一张图片上,绘制地图时直接从素材区拖动来批量框选,再在地图区单击成片绘制或拖动平铺。 + 3. 批量替换也更为方便。譬如您如果想制作形如“一场大战/天灾过后/多年以后,村庄/城镇化为了一片废墟”的效果,可以预先准备两张甚至更多张相同规格的额外素材图片,然后在适当的时候遍历某个/某些楼层的图块ID,将以“X1”开头的图块统一替换为“X2”开头等。发布单机版游戏时,您也可以提供多张相同规格的额外素材图片供玩家直接替换皮肤风格。 +5. **使用动画:**`project/animates`文件夹中的文件名(不含后缀,请注意与`animates.png`相区分)。 + * 要使用动画,您可以使用“RM动画导出”工具从RPG Maker XP 1.03及其制作的游戏中导出动画,也可以用动画编辑器修改已有的动画或用图片新建。但这些办法都只适用于Windows系统,非Windows系统建议直接从我们的官网下载他人的作品取用其中的动画。 + * 每个动画将其用到的各张图片直接以base64硬编码进同一个animate文件,每个动画为一个animate文件。这样做的缺点是如果多个动画使用了相同的图片那么图片会被重复存储,优点则是跨作品迁移动画更加方便。 + * animate文件为json格式的文本文件,文件末尾记录了动画的帧信息,文件开头则记录了动画的伸缩比和所有音效(第几帧用什么音效,音调是多高)。 + * 在导出动画时,会出现一个输入框并提示动画的第一音效文件名。不管该文件名是什么语言,请直接点击下一步。音效文件会被尝试自动复制,随后您只需手动注册该动画并在编辑器中预览,然后重新按帧绑定需要的音效。 + * 可以使用如下动画相关的脚本对动画进行播放,或在事件中使用「播放动画」事件。 + ``` js + core.drawAnimate(name, x, y, alignWindow, callback); + // 播放一个动画,name为不带后缀的动画文件名,x和y为播放的格子坐标。 + // alignWindow表示在大地图中该坐标是绝对坐标还是视野中的相对坐标,填true表示相对坐标。 + // 相对坐标模式下,坐标应填写为小于13或15的自然数,譬如对13×13样板来说, + // 填两个6就会强制播放在视野中心。 + // callback为动画播放完毕后(和音效无关)的回调函数,因为动画播放本身是异步的。 + core.drawHeroAnimate(name, callback); // 和上面类似,但该动画会跟随勇士移动。 + // 每场战斗后,都会根据怪物坐标尝试用前者播放普攻动画。若坐标不存在, + // (即天降强制战斗),则会尝试用后者播放。看上去就像勇士在打自己,请自行注意。 + core.stopAnimate(id, doCallback); // 停止一段动画,id为上面两个函数的返回值, + // 第二个参数表示本次停止后是否依然执行前两个函数的回调 + + // 您可以用前两个函数回调自己,从而做到一个动画反复循环, + // 但务必要将每次的id记录在flags中,以便于第三个函数去停止它。 + ``` +6. **使用音乐:**`project/bgms`文件夹中的文件名(需要后缀,默认只支持wav、mp3、ogg、m4a、flac)。 + * 如果玩家使用的是手机且没有连接WiFi(iOS和部分浏览器无法获知网络状态,将始终视为流量党),那么背景音乐默认不会开启,可以在标题画面点击右下角的圆形按钮来开启。 + * V2.8起,这个圆形的音乐开关旁边还增加了一个放大镜按钮,您可以在标题画面连续点击它来获得一个最佳的屏幕缩放倍率。 + * 发布到官网的作品还可以从远程加载背景音乐,您可以点击此链接试听和下载其他作品的背景音乐。 + * 是否启用远程加载、以及启用时远程加载的根目录,由main.js指定。因此从官网下载其他作品的离线版本后请先关闭远程加载功能,才能正常加载本地注册的音乐。 + * 使用`core.material.bgms`可以查看所有的背景音乐,如果需要(不存档的情况下)同时播放多个背景音乐并独立控制时刻、音量、速度、音调,请直接对它们使用`play()`和`pause()`方法并按照下图操作。 + * `duration`表示总时长(秒,音效也适用),`currentTime`表示当前进度(秒,可读写) + * `volume`表示音量(0到1),`playbackRate`表示播放的倍速 + * `preservesPitch`表示变速播放时是否不变调,`false`表示变调 + * V2.8起,背景音乐“变速不变调”和“变速且变调”、音效“变速且变调”正式作为样板API提供,同时也有对应的事件指令,这是样板在追逐RPG Maker上迈出的一大步,且行且珍惜! + * V2.8.1起,动画的音效支持“变速且变调”,同时编辑器中试听音频也支持了(但bgm试听不能只变速不变调)。 + ![image](img/bgm.jpg) +7. **使用音效:**`project/sounds`文件夹中的文件名(格式要求和写法同上)。 + * V2.8起,音效的播放和动画一样会返回一个ID并且有回调。音效不能像bgm一样只变速不变调,而是必须一起变。 + * 可以指定ID来提前停止某个音效(不指定ID则全部停止),但不能像停止动画时一样取消回调。 + * 对玩家来说音效的音量和bgm是绑定的,无法分开调节。此外,样板没有背景音效(bgs、bgv)的默认实现。如有需求,请使用并行脚本或自我回调处理。 + * 音乐和音效在使用多选框注册时都支持试听(但此时不支持变速变调),您可以看到它们的总时长和已播时长(精确到秒),从而指定音乐的开播秒数或配合使用“等待n毫秒”事件或并行脚本处理。 +8. **使用字体:**project\fonts文件夹中的文件名(只支持ttf格式,不写后缀)。不建议给在线游戏版本添加中文字体,因为文件真的很大... +9. **文件别名:**如前所述,样板所有需要加载的文件名都必须全部是英文或数字。这一项允许你给文件取别名,别名可以使用任何语言的文字。 + * V2.8起,此项改由事件编辑器编辑,同时样板新增了大量系统音效,可供灵活配置,务必善用! +10. **装备孔:**见“道具的装备属性”。 +11. **标题音乐:**如题,请注意部分浏览器不会在刚打开某个页面时就播放音频,必须用户操作一下才行。 + * V2.8起背景音乐支持“变速不变调”或“变速且变调”,但无法直接用于标题音乐。如有需求,请复写`core.control.showStartAnimate`函数。 +12. **主样式:**一些css设置项,单击“编辑”按钮可以看到具体含义和用法,这里不再赘述(横竖屏标题画面背景支持gif动图)。 +13. **游戏名:**标题画面和网页选项卡上显示的名字,可以和官网别的作品重名。 +14. **唯一英文标识符:** **必须修改,且不得和官网别的作品重名**。只能使用字母数字下划线,如果您不确定一个标识符是否已被别的作品使用,请输入`https://h5mota.com/games/xxx`,如出现404就说明此名称未被使用。 +15. **游戏版本:**当您的游戏发生版本更迭后,旧版本的存档直接读取可能会出现bug.因此届时您可以修改此项来让旧存档无法直接读取,只能回放其录像。 +16. **难度分歧:**单击“编辑”按钮进入事件编辑器,每个难度分为以下几项。 + * 名称:标题界面单击“开始游戏”后出现的二级菜单中的文字。一般为该难度的最简要的介绍,如减伤比例、初始福利等。 + * 简写:横屏状态栏左下角(竖屏右下角,也作为数字键切换按钮)和存读档界面缩略图上的文字,也会出现在在线游戏排行榜和旧版官网的作品卡片上。允许使用中文但请注意控制字数,用`core.status.hard`表示。 + * 变量hard值:即`core.status.hero.flags.hard`的值,建议填自然数。若同一结局有多个难度有人通关,则站点排行榜只统计其中此值最高的难度。请务必保证有一个难度的此值为0,理由见下。 + * 颜色:上述“简写”的颜色,用`core.status.hero.flags.__hardColor__`表示,默认为红色。详见“脚本编辑——更新状态栏”。 + * 事件:此事件比下述的“开场剧情”更早执行,一般用来设置初始福利。 + * 如果将难度分歧数组留空,那么标题界面单击“开始游戏”就会直接进入开场剧情。 + * 请注意,游戏中的难度分歧最好只通过`core.status.hero.flags.hard`进行,这是因为除非开启了标题界面事件化,否则`core.status.hard`是可以由玩家在标题画面任意指定的,而未定义过的难度的“变量:hard”值都会为0,这也是为什么上面要求务必要有一个难度指定0值的原因。 + * 关于如何在标题画面任意指定难度和随机种子,参见[事件](event)一章的最底部。 + +### 初始勇士 + +1. **初始楼层、朝向和坐标:**如题,请注意初始楼层必须在上述的“楼层列表”中。初始坐标一般通过右击地图上的空地快速绑定,但您也可以手动在这里填写负数或超出初始楼层宽高的值。然后使用“无视地形移动勇士”或“跳跃勇士”等改变勇士位置的事件指令,做出“勇士从地图外进入”的演出效果。 + * 如需根据难度分歧或用户选项等条件来改变它们,请在“开场剧情”中修改`core.firstData.floorId`和`core.firstData.hero.loc` +2. **行走图:**如题,必须在“使用图片”中注册过。宽高必须为4的倍数,宽度至少为128px(即每帧32px)。高度不限,剧情对话中和状态栏中会尝试保持比例压缩到每帧32px宽。 + * 在游戏中,勇士当前的行走图文件名用`core.status.hero.image`表示(只读) + * 游戏中如需更改行走图请使用事件指令,但请注意它并不会计入站点录像验证,而且更换新行走图后如果立即重绘勇士就会重置视角。 +3. **帧动画:**勾选此项后,勇士在剧情对话中(朝上视为朝下)和原地不动时会循环播放四帧踏步动画,一般用于长翅膀的勇士。但是勾选后会导致视角随时锁定在勇士身上,也就是说大地图中操作视角的指令都会失效! +4. **勇士名:**如题,也会作为剧情对话中`\t[hero]`的默认标题。 +5. **初始等级:**如果开启了自动进阶功能,请不要修改此项。 +6. **生命上限、魔力上限、初始生命/魔力/攻防/护盾/金经:**如题。注意生命上限和金经需要在系统开关中勾选后才会启用,魔力上限填负数代表没有上限。 +7. **初始装备、游戏变量:**建议留空(事件中的变量初始时都会视为0,脚本中也有办法做到)。 +8. **永久道具、消耗道具、初始拥有装备个数:**点击“注释”按钮,按照提示进行修改。 +9. **标题事件:**需要配合系统开关中勾选“标题开启事件化”来使用,可以在“开始游戏”、“读取存档”之余添加一些额外的功能。如成就系统、音像和回想鉴赏,但这并不是唯一的方法,请自行研究。 + * 使用此项的后果主要是开局的若干操作也会计入录像,观感较差。 + * 使用此项后,难度分歧会通过用户操作(如显示选择项)实现,因而成为了录像的一部分。 + * 使用此项后,会导致因为无法像默认标题一样做成长方形,只能采用“黑色边框+状态栏黑底+隐藏状态栏”的妥协方法得到一个偏右的正方形画面。 + * V2.8提供了一个插件,可以将这个正方形画面暂时居中,这是没有办法的办法。 +10. **开场剧情:**会在难度分歧事件之后执行,可以在这里设置各种变量的初始值、穿上初始拥有的装备、隐藏勇士和一些初始不希望显示的图层块、追加跟随者等。 + * 需要注意的是,此时`core.status.floorId`还没有值,因此无法进行相关操作,包括存档! +11. **全局商店:**详见“QTE与全局商店”。 +12. **等级提升:**需要配合系统开关中勾选“等级”、“经验”和“升级”来使用,每个等级分为以下几项: + * 需求:刷新状态栏时,如果勇士当前等级是此等级的前一级,且经验值大于等于此需求,就会触发升级。 + * 称号:状态栏显示的等级默认是个正整数,会尝试替换为这里的称号(调用core.getLvName()函数),请注意控制字数。 + * 是否扣除经验:如果勾选了此项,则触发升级时经验值会扣除需求值,这一项主要是为了应对一次升多级的情况。 + * 事件:触发升级时执行的事件,如全面提升属性。事件中甚至可以降低等级,从而反复触发升同一级。 + +### 全局数值 + +![image](img/values_flagsb.jpg) + +这个类型的数值会保存在core.values中,可以直接在游戏中修改。 + +1. **血网伤害和中毒伤害:**如题,如果不想用常数,请修改“脚本编辑”的“阻激夹域伤害”和“每步后操作”。 +2. **衰弱效果:**填小于1的正数代表扣减的比例,否则为扣减的常数。扣减和恢复的原理和装备相同,详见“脚本编辑——毒衰咒处理”。 +3. **三种宝石和四种血瓶的值:**如题,此值为基础值。实际效果还需乘以楼层属性最下面的“宝石血瓶效果”(限即捡即用类,详见这七种道具的属性)。 +4. **反击、破甲、净化比例:**如果反击、破甲、净化怪物没有指定atkValue、defValue和n,就会用这三个值。请注意反击的总回合数是勇士的攻击回合数,净化比例填1表示单纯无视护盾。 +5. **仇恨增值:**每进行一场战斗,core.status.hero.flags.hatred的增加量。如果不想用常数,请修改“脚本编辑——战后脚本”。 +6. **全局帧动画时间:**即怪物和NPC的振动速度,建议改为300毫秒。 +7. **竖状态栏自绘行数:**需要配合系统开关“开启自绘状态栏”使用,建议改为4. +8. **楼层切换时间:**此项可被玩家更改,单位为毫秒,必须为0.1秒的0到10倍。 + +如有需求,可以把勇士移速(可被玩家更改)通过“配置表格”追加到这里,并修改`libs\core.js`对上述数值的初始化行为。 + +### 系统开关 + +这个类型的开关会保存在core.flags中(只读),请注意和core.status.hero.flags相区分。如需在游戏中修改,请使用“设置系统开关”事件。 + +1. **状态栏显示项:**如题,总个数请控制在12个以内,否则竖屏可能塞不下。 + * 这些项的图标都在`project\materials\icons.png`中。该文件夹下也提供了一个`icons_old.png`可供替换。 + * “血限”、“金币”和“经验”必须勾选才会启用(指会处理生命溢出、金经会显示在手册、打怪也会掉落), + * 必须勾选“升级”才会启用自动进阶,“升级扣除模式”如果不勾选就会同时显示下一级的需求(NEXT)和当前经验(EXP,如果勾选了的话),否则会只显示两者的差(依然用NEXT图标),“扣除”这个词用的不太好。 + * 如果同时勾选了“钥匙”和“绿钥”,则每种钥匙的数量都会被缩小尺寸显示,因此如非必要请不要勾选“绿钥”。 +2. **楼传需在楼梯边:**如果勾选了此项,则只有勇士在楼梯旁边(包括六种portal旁边)时才允许使用楼传。 + * 请注意,此项是在楼传道具使用条件之外额外进行的判定,目的是给出不同的提示信息。 + * 因此如果您要修改楼传的使用条件(指总的使用条件,具体能否飞到某层的条件则在“脚本编辑——楼层飞行”),则可能需要一并取消此勾选。 + * V2.8起,浏览地图界面按下G键或点击楼传图标则会尝试飞到当前正在浏览的楼层,这大大方便了玩家。 +3. **楼传开平面模式:**如果勾选了此项,则勇士在使用楼传飞往某层时会落在上次离开该层的位置(锚点)。 + * 此项和上一项建议要么同时勾选要么同时不勾选,同时勾选以后建议在“脚本编辑——flyTo楼层飞行”中禁止同层传送,否则可能有意想不到的后果。 + * 楼层切换过程中可以根据局部变量`fromLoad`和`isFlying`进行不同的处理(例如播放不同的音效),前者表示是否为读档,后者表示是否为楼传,建议由这两者导致的楼层切换一律不更新锚点。 +4. **铁门不消耗钥匙:**如果勾选了此项,则铁门无需钥匙也能撞开。【不建议使用!】因为此项会导致撞铁门时不再自动存档,因此建议修改铁门的`doorInfo`属性将钥匙栏留空即可。 +5. **首次道具进行提示:**勾选后,首次捡到非即捡即用类道具都会弹窗提示(晚于`afterGetItem`事件被执行)。 +6. **状态栏装备按钮:**勾选后,状态栏的楼传按钮会变为装备栏按钮,但玩家仍然可以双击道具栏来呼出装备栏。 +7. **加点:**勾选后怪物的加点值会在“脚本编辑——战后脚本”中作为参数`core.status.hero.flags.arg1`被传递给“公共事件——加点事件”。 +8. **负伤:**勾选后,战斗结束时如果勇士的护盾没有完全被打破,则剩余的护盾值会加到其生命上。所以勾选此项后,护盾可以这样“间接”抵消掉仇恨伤害和固伤。 +9. **夹击不超伤害值:**勾选此项后,夹击伤害将封顶至夹击怪的战损。同时被四只怪夹击时,取两个战损中较小的。 +10. **二分临界:**我们知道,打败怪物所需的回合数,取决于勇士的攻击减去怪物的防御。这个值并非每增大1都能使回合数减少,因而有了“临界”的说法,即“再至少增加多少攻击力,才能减少回合数”。然而,当您修改“脚本编辑——战斗伤害信息”函数后,攻击力的增加可能反而导致回合数也增加,于是临界值计算出错。您可以勾选此开关来修复,代价是游戏可能较卡,请自行权衡。 + * 目前样板的临界只有回合数法和二分法被真正采用,而循环法则只是保留了代码。如需启用,请修改`main.js`中“循环临界的分界”。 + * V2.8.1起,未破防怪的临界表中,第一项将用负数表示刚刚破防时的伤害,后面的项正常表示破防后再加攻击的减伤。 +11. **标题开启事件化:**勾选此项后,标题画面将改为执行前述的“标题事件”,请自行研究,V2.8起建议配合“标题事件居中”插件。 +12. **开启自绘状态栏:**勾选此项后,状态栏将改用“脚本编辑——自绘状态栏”来绘制,同时“脚本编辑——点击状态栏”也将启用,请自行研究。 +13. **四个显伤:**略,玩家依然可以在设置菜单中开关之,其中“定点怪显”指的是手册中把受到额外影响而数值不同的同种怪物分开显示。 +14. **允许轻按:**勾选此项后,玩家可以按下空格/大键盘数字7/双击勇士来拾取相邻的道具(优先面前)。 +15. **允许穿透楼梯:**在狭窄的区域拦路放置一个可通行的“楼层转换”事件时(图块左下角出现绿色标记),玩家可能希望勇士能直接走过去。您可以逐个去设置其能否被这样走过,或者让其依据本勾选项。 + * 值得注意的是,当玩家从允许穿透的楼梯向一个不可走的方向(如旁边是墙,或不勾选下一个开关时的致命领域)手动寻路时,可以停在楼梯上(进而再轻按拾取周围物品等)。不建议您利用这类极端情况去迫使玩家进行非常规操作,毕竟穿透楼梯和不能踏入致命领域的本意是方便玩家,不是么? + * 确切地说,即使旁边没有障碍物,录像意义下勇士也一定能停在任何可穿透的楼梯上。因此笔者再次强调,非常不建议利用这样的盲点戏弄玩家。 +16. **允许将死领域:**“脚本编辑——阻激夹域伤害”会将地图中每个点的阻激夹域和血网伤害加总,如果不勾选此开关,则当勇士生命小于等于相邻空格子的总伤害(没有伤害则没关系)时,勇士无法走向该格子。 + * 值得注意的是,这种判定方式并没有考虑“走这一步后、结算该点伤害前”可能的加血或该点伤害变化,因此如有必要可根据“脚本编辑——每步后操作”去修改core.canMoveHero()函数。 + * 另外,此项的保护作用只会在“勇士可以自由行动”(`core.status.lockControl`为`false`)时生效,因此事件中(滑冰等场合)踩到致命领域仍然会死亡。 +17. **允许瞬移:**若不勾选此开关,将全程禁用瞬移功能。一般只建议在需要的楼层逐层勾选禁止瞬移,或通过`flags.cannotMoveDirectly`来控制。 +18. **录像折叠:**勾选此项后,将开启录像折叠功能。录像折叠将尽可能优化掉在一个地方无意义的行走,从而减少录像长度并提升播放观感。 + * 当经过一段时间的行走、转向和瞬移后,若勇士的坐标、朝向和状态(步数除外)和之前某个时刻完全相同,那么将会直接删除这中间的录像记录。 + * 当中毒状态、触发任何系统或自定义事件、图块脚本、楼层切换、受到阻激夹域伤害等等时,将清除录像折叠信息。 + * 请注意:录像折叠将会优化步数,所以如果游戏和步数有直接关系(比如步数算分)请关闭录像折叠功能。另外,如果你的塔存在楼层并行脚本且对游戏数据有直接影响,也请关闭录像折叠功能。 +19. **伤害禁用商店:**勾选此项后,每当勇士踩到阻激夹域和血网并受到伤害时,所有全局商店都将被禁用,需要重新去启用(譬如勇士去撞击该商店的实体NPC)。 +20. **虚化前景层:**前景层会遮挡事件层,这对魔塔来说有时可能不太友好。勾选此项后,事件层有东西(如道具)时将虚化该格子的前景层,使得该东西以半透明状态可见。 + * 出于布景需要,某点事件层为自动元件或tileset时,必须有“碰触脚本/碰触事件”,该点前景层才会虚化。 + +上面就是整个样板中的各个元件说明。通过这种方式,你就已经可以做出一部没有任何事件的塔了。 + +尝试着做一个两到三层的塔吧! + +========================================================================================== + +[继续阅读下一章:事件](event) diff --git a/_docs/event.md b/_docs/event.md new file mode 100644 index 0000000..e033e0a --- /dev/null +++ b/_docs/event.md @@ -0,0 +1,329 @@ +# 事件 + +?> 在这一节中,让我们来了解事件系统的基本原理 + +## 事件编辑(地图选点,快捷键X) + +![image](img/events.jpg) + +样板所有的事件都是依靠“触发器”完成的。例如,勇士踩到(绑定好的)楼梯可以触发changeFloor,碰到门可以触发openDoor,碰到怪物可以触发battle,踩到/轻拾道具可以触发getItem,推到箱子可以触发pushBox,走上冰面(背景层)可以触发ski. + +这些触发器都是系统自带的(称为系统事件),已经存在处理机制,不需要我们操心。我们真正所需要关心的,其实只是自定义的事件(如NPC)。 + +地图选点(快捷键X)一共有八项,在地图中以图块左下角用八色小方块标记。 + +* **红色:**普通事件,也包括该点的一些特效。 +* **橙色:**自动事件,可以设置为“仅执行一次”、“仅在本层检测”。 +* **暗绿色:**战前事件,强制战斗时不触发。 +* **黄色:**战后事件。 +* **亮绿色:**楼层转换,可以设置为“可穿透”。 +* **青色:**获得道具后事件,可以设置为“轻按时不触发”。 +* **靛色:**不是事件,为勇士站在该点不能朝哪些方向走。 +* **紫(粉)色:**开门后事件。 + +此外还有一些事件不在地图上,如“首次/每次到达”(这两个的触发原理,详见“脚本编辑——切换楼层后”)、“道具效果”、“碰触事件”、“(批量)战前/战后事件”、“公共事件”和全塔属性中那五个事件。 + +### 事件的机制 + +地图上的所有事件都存在两种状态:启用和禁用。 + +* 启用状态下,该事件才处于可见状态,可被触发、交互与处理。 +* 禁用状态下,该事件几乎相当于不存在(默认可以被阻击怪和箱子推上去),不可见、不可被触发、不可交互,只能通过“插入事件”指令远程调用。 + +所有事件默认情况下初始都是启用的,只有普通事件可以通过不勾选“启用”来初始隐藏。 + +在事件列表中使用“显示事件”和“隐藏事件”可以将一个禁用事件给启用,或将一个启用事件给禁用,甚至直接从地图中删除。 + +**【重要警告】:** +1. 打怪开门捡道具后,如果有对应的坐标,则该点的事件会被从地图中删除(重生怪除外,它只会被暂时隐藏)。然后会尝试执行该点的战后事件、开门后事件或(拾获)道具后事件(您可以勾选“轻按时不触发”)。如果执行,则“当前点坐标”(`core.status.event.data`的坐标)也会变为该点。 + * 所以,这三个XX后事件,其实是有可能被多次触发或意外触发的(如打死或炸掉某个有战后事件的怪,推了个阻击怪过去打死),请自行注意。 +2. “移动事件”(如阻击和捕捉)和“跳跃事件”(如支援)会将起点处的事件从地图中删除,如果不勾选“不消失”(如阻击),则会在终点处“转变图块”并“显示事件”。但事件的内容不会被跟着移动到终点,推箱子同理,但在终点只转变图块而不予显示。 + +### 楼梯、传送门事件 + +![image](img/changefloor.jpg) + +当您在地图上绘制楼梯、或绘制四种三帧箭头并右击绑定后,就创建了一个“楼层转换”事件,您可以在事件编辑器右侧看到一行代码(json),请注意对照。 + +此事件与`core.changeFloor(floorId, stair, heroLoc, time, callback)`函数相对应,只是多了一项“穿透性”。 + +每个这样的事件都是上图的写法,下面逐一讲解其各项的含义和用法: + +1. **目标楼层(floorId):**如题,如果选择了“楼层ID”就需要在第二个框中输入目标楼层的ID(会自动提示补全,也可以双击整个紫色块从地图选点)。如果选择了其他三种,则json代码中`:before`、`:next`、`:now`分别表示“前一楼”、“后一楼”(顺序由“全塔属性——楼层列表”指定,上下楼器同理)和“当前楼”,函数中也是一样的写法。 +2. **目标位置(stair):**如果选择了“坐标”则需要在后面两个框里输入(同样可以地图选点,在函数中则将`stair`填`null`并填写`heroLoc`),也可以选择下拉框中的其他几项(在函数中则将`heroLoc`填`null`),如保持不变(`:now`)、中心对称(`:symmetry`)、左右对称(`:symmetry_x`)、上下对称(`:symmetry_y`)或任何一个图块ID. + * 填写三种对称位置时请注意,坐标计算以【当前楼层的勇士】位置(对不可踏入的楼梯来说,这个位置与楼梯相邻而不重合)而不是目标楼层或该楼梯为准,注意防止勇士出界。 + * 填写图块ID时请注意,“上下楼梯”和“楼传落点”提供在了下拉框中,实际传送时会优先尝试取用目标层的“楼层属性——上下楼点”。其次尝试像其他ID(必须写在右侧的json区再点击解析)一样从目标楼层搜索,因此请确保该图块在目标楼层中存在且唯一。 +3. **朝向:**有八种写法,可以直接填写改变后的朝向(`up, down, left, right`),也可以填写转身的角度(`:left, :right, :back`)或不变。 +4. **动画时间:**指黑屏的毫秒数,可以填0或不小于100的整数,不填则使用玩家设定值。 + * V2.8起,楼层切换的音效被延迟到了“屏幕完全变黑”的瞬间(脚本编辑——切换楼层中)才播放,以方便作者根据条件设置不同音效,因此上下楼时间建议尽量缩短。 +5. **穿透性:**见[系统开关](start)允许穿透楼梯。 + +值得注意的是,绑定了楼层转换事件的门、怪物、道具、箱子,其系统行为(开门、战斗、拾取、推行)将不再发生(阻激夹域等地图属性不受影响),而是被覆盖为上下楼。 + +## 普通事件(红) + +普通事件在启用后可以被勇士碰触而触发,它们都有着这样的开头: +``` +覆盖触发器(Y/N) 启用(Y/N) 通行状态(Y/N?) 显伤(Y/N) +V2.8新增:不透明度(0~1) 虚化 色相(0~359) 灰度(0~1) 反色(Y/N) 阴影 +``` +1. **覆盖触发器:**如前所述,门、怪物、道具、箱子都已经被绑定了系统触发器。如果您需要让地图上的个别这类图块在被勇士碰触时不发生系统行为(开门、战斗、拾取、推行),而是执行您在下面自定义的事件(比如需要制作一个“怪物中的叛徒”,它虽然有显伤但其实是个npc),请勾选此项。(阻激夹域捕捉支援等地图属性不受影响) +2. **启用:**如果不勾选此项,则此事件初始时是隐藏的。 + * 非常不推荐仅仅为了初始隐藏一些图块(如战利品、陷阱门墙、埋伏怪等)就去绑定一堆没有指令的普通事件。 + * 更安全的做法是“从0转变图块”(支持淡入效果)和“关门”(这样还有音效,多香)。 + * 事实上,除“覆盖触发器”外,其余所有参数本不该作为“普通事件”的一部分而应该移出去放在表格中,这是样板的历史遗留问题。 + * 如今V2.8加入了六项特效,可能导致无指令的普通事件越来越不可避免,请自行注意。 +3. **通行状态:**除怪物和道具外,所有图块本身都具有“可通行性”属性。 + * 您可以设置此项去覆盖地图中这一点的可通行性,譬如强行让一格空地不可通行,勇士撞击时出现一堵墙(51层魔塔第23层的效果)。 +4. **显伤:**在对怪物使用覆盖触发器后,表面上看不出它和一般怪物的不同。 + * 如有必要(比如触碰这怪物根本不战斗),可不勾选显伤以示区别。 + * 如数量较多,可注册一个相同素材的NPC,比如样板中的仙子。 + * 如需在一段剧情中关闭所有显伤,可以简单地暂时移除怪物手册 +5. **不透明度:**如题,0表示完全不透明,1表示完全隐身。可以设为1用来配合楼层贴图实现大型怪物,或者设为半透明来表示灵魂等状态的角色。本属性可以在事件中用指令动态修改,支持渐变。 +6. **虚化:**必须为自然数,设定后图块会变模糊,可以用来配合快速移动/跳跃来表示幻影状态的角色。不建议对多帧图块长时间使用,因为会随着不断的抖动把周围越描越黑。 +7. **色相:**必须为0~359的整数,180表示互补色。常用于npc以避免过于单调,也可以在游戏的教学环节用此项高亮强调部分图块。 +8. **灰度:**如题,1表示完全变灰,必要时可以全图变灰来表示回忆/梦境/濒死等场合。 +9. **反色:**勾选后此图块将反色,必要时可以全图瞬间反色来表示惊吓。 +10. **阴影:**必须为自然数,设定后图块周围将出现阴影描边,在草地上效果更佳哦。 + +可以使用`core.setBlockFilter`更改任何一点的最后五项特效。 + +## 自动事件(橙) + +在“右键绑定机关门”中,我们已经初见了自动事件的端倪。 + +在游戏中,我们经常需要监听大量乱七八糟的状态,根据状态的变化去做出及时的处理,比如勇士生命小于等于0会触发游戏失败。比如51层魔塔第39层监听9扇黄门的状态来触发中心对称飞行器的出现、第49层监听8名魔法警卫的状态来封印假魔王。 + +如果给9扇黄门都绑定类似的开门后事件、给8名警卫都绑定类似的战后事件,就非常繁琐。 + +而自动事件则不然,它不需要您去主动考虑所有“改变状态”的原因,而是简单地在每次刷新状态栏时,检查是否满足触发条件,满足的话就执行。 + +每个自动事件具有以下几个属性: + +1. **触发条件:**自动事件最重要的属性,为支持“冒号缩写量”的逻辑表达式。 + * 譬如上述两个例子中,触发条件就是“某两个点没有图块而某7个点图块为黄门”和“某四个点没有图块而某四个点图块为魔法警卫”。 + * 关于冒号缩写量,详见下面的“值块和冒号缩写量”。 + * V2.8起,自动事件的触发条件和“值块”支持多行编辑(eval时会忽略\n)。 + * 因此高级作者可以将此条件写成无名函数,这样就可以在里面使用var进行复杂的多步计算了。 +2. **优先级:**一般不需要改动。当多个自动事件的条件同时满足时,优先执行此级别较大的。 + * 此级别也相等时,允许跨层触发则优先执行楼层较矮的。 + * 同一楼层优先执行横坐标较小的,横坐标相等时优先执行纵坐标较小的,同一点处优先执行页码较小的。 +3. **仅在本层检测:**如题,一般不需要改动。除非您要制作一些全局的效果,如“勇士生命低于上限的1/3时改变背景音乐”。 + * V2.8起,即使不勾选此项,也不会在分区砍层模式下触发冻结状态楼层的自动事件! +4. **事件流中延迟执行:**勾选此项后,在执行其他事件时触发此自动事件则会将其内容追加到待执行事件的末尾,否则插队到待执行事件的开头。 +5. **允许多次执行:**如果不勾选,则此事件最多只会被触发一次。即使勾选了,每次执行的过程中也会暂时禁止自己被触发。勾选后,请记得在此事件的指令中将条件变得不满足。 + +每个点初始提供了两个自动事件页,您还可以再在数据区最上面单击“添加自动事件页”。 + +## 公共事件(逗号键) + +有几个事件,如战后加点、回收钥匙商店,会在战后脚本或全局商店等处频繁用到。于是它们被整理成了公共事件,您也可以追加自己的。 + +公共事件在内容上和其他事件并无不同,只是在插入公共事件时(如`core.insertCommonEvent("回收钥匙商店",[...])`)可以提供一个`参数列表`。 + +参数列表是一个一维数组,其每项会被依次代入`core.status.hero.flags.arg1、arg2、……`,而`arg0`则会记录公共事件的中文名。 + +在整个事件流结束后(即勇士恢复行动,请注意不是公共事件结束后),这些以arg加纯数字命名的变量、以及以@temp@开头的临时变量都会被清空。 + +## 滑冰事件 + +滑冰(ski)是一个非常特殊的触发器,使用此触发器的图块需要画在背景层。 + +冰上可以画门、怪物、道具等任何图块,您还可以在前景层画上四种单向通行箭头(样板1层右下方那些)或红线薄墙(素材区第一列最下方那些),把三个图层的游戏性都发挥出来。 + +勇士走上冰面后,将一直“前进一格或撞击”。直到滑出冰面或无法再前进,如撞到墙或事件。 + +比如撞到怪物会强制战斗,打不过直接游戏失败。走到道具上会捡起来,撞到门则要看有没有钥匙,有的话会把门打开。V2.7.3起,踩到致命领域会死亡而不是停在前一格。 + +默认情况下,触发事件后勇士会停下来。如果要继续滑冰,可以在这些点的战后事件、开门后事件、(拾获)道具后事件(这里最好勾选“轻按时不触发”)中判断勇士还在不在冰上(`core.onSki()`函数),在冰上的话就命令“勇士前进一格或撞击”。 + +## 事件编辑器 + +![image](img/blockly.jpg) + +当您从数据区单击任何一个事件类属性的“编辑”按钮时,就会呼出事件编辑器,上图给出了事件编辑器的全貌。 + +图中左下角为指令类别,可以点击它们呼出各类指令菜单,如左上方的值块菜单。 + +中央为指令区,被一个标识事件类型的紫色块包覆(即左下角的“入口方块”,图中为开门后事件),所有的指令都塞在里面。 + +右上角为json区,会随着指令区的编辑而自动变化。`core.insertAction()`函数的自变量就是它们,所以学有余力的话也建议您经常左右对照去了解每个指令对应的json写法。 + +右下角为调色器,当您单击指令块中的颜色按钮时(如图中“画面闪烁”指令的白块)就会自动呼出,可以进行诸如十进制和十六进制颜色格式互转并预览等操作。 + +左上角为功能区,各功能的含义和用法如下: + +1. **确认(Ctrl+S):**将指令区的指令组翻译为json写回表格和文件,并关闭事件编辑器。如果手动修改了json区却没有点击变黄的解析按钮,或指令区存在异步事件没有用“等待所有异步事件执行完毕”(图中第二条褐色指令)去等待,则单击确认按钮会弹窗警告。此时如果想放弃对json区的修改,请随便点一下指令区的一个块即可。 +2. **解析:**将手动修改后的json区翻译回指令组同步到指令区,一般用于跨作品复制事件,或长距离批量挪动和复制指令。此外,由于下拉框不支持任意输入(如新增的怪物属性、楼层切换的任意目标图块ID),所以也必须这样做。 +3. **取消:**放弃本次编辑,关闭事件编辑器,表格和文件会停留在打开事件编辑器前的状态。 +4. **搜索事件块:**当您想不起来需要的指令在哪个类别时,请使用此项,如输入“勇”字就会列出所有带有“勇士”一词的指令。 +5. **地图选点:**在编辑事件尤其是大篇幅事件的过程中,如果需要频繁保存并退出事件编辑器去浏览地图尤其是浏览别的楼层就很不方便。单击此按钮,您可以浏览所有楼层及其全景。通过点击地图去确认一个点的坐标,并复制任何一个楼层ID以填入需要的地方。需要填坐标的指令也可以直接双击其指令块从地图选点填入坐标和楼层,从而避免了肉眼数格子和手敲的麻烦和出错的隐患。 +6. **变量出现位置搜索:**您可以搜索所有以“变量:xxx”形式出现在事件中的变量的出现位置,形式必须是冒号缩写量,位置必须是事件而不是脚本。也就是说,`flags.xxx`和`core.insertAction([...])`中的变量都是搜索不到的。 +7. **开启中文名替换:**勾选此项后,“flag、status、item、switch、temp、global、enemy”等冒号缩写量的前缀以及怪物和道具的ID才会允许用中文在指令块中书写,如“变量、状态、物品、独立开关、临时变量、全局存储、怪物”。当然,json中还是只能用英文的。此外,如果您需要使用名称相同的道具或怪物,或在`${}`以外的文本中将上述几个词和冒号连用,则也可能需要关闭此功能以避免blockly和json互译出错。 +8. **展开值块逻辑运算:**在值块菜单中我们可以看到第一个就是二元运算块,但是它并不完整,比如没有位运算。因此如果解析的优先级与js的优先级不一致,请在表达式中适当的位置添加圆括弧,或取消勾选此项。 + +## 值块和冒号缩写量 +![image](img/values.jpg) +值块并不能单独构成指令,但它们可以被嵌入数值操作、设置怪物属性和很多事件控制类指令。而冒号缩写量的用法就更多了,比如用于`${表达式计算}`和其他任何支持填表达式的地方。 + +值块大体上分为三种:勾选框与运算块、只读块、可读写块,其中后两类对应着冒号缩写量。 + +### 勾选框与运算块(附js比较运算大图鉴) + +包括勾选框块(`true`和`false`两个逻辑常量)、否定运算块和二元运算块。 + +否定运算块(一个“非”字,脚本中写作一个叹号)会把`false、0、null、undefined、NaN`和空字符串”(以下简称广义`false`)变为`true`,而把别的量都变为`false`. + +二元运算块包括四则运算、取余、乘方、比较运算和三种逻辑运算。 + +四则运算中,两个整数相除会得到小数,两个0的乘方会得到1(数学上无意义)。 + +八种比较运算中,四种比大小的运算如果两项中有一项是数字,就会把另一项也转换成数字去比,所以会出现这些: + +`'2' < 10; 10 <= '10'; '10' < 2; null >= 0; null <= 0; 1/0 > 1/-0` + +![image](img/compare.jpg) + +弱(不)等于(==和!=)非常难用,建议放弃,统一使用(不)等于(===和!==)。 + +`NaN`一般是`0/0`或字符串瞎做减乘除得到的,它跟谁都没有可比性,包括自己。 + +纯数字字符串(包括十六进制)和对应的数字,都是弱相等关系。如`'0x10' == 16` + +数组和对象,跟自己都是既大于等于又小于等于的关系(因为不是同一个)。 + +单个数字(或什么都没有)用方括号还是引号括起来,对外的比较行为都一致。 + +`undefined`倒是不用担心,它除了和`null`弱相等外,和其他值都没有可比性。 + +三种逻辑运算的原则是,“且”的优先级高于“或”,它俩都不满足交换律。 + +若干个由“且”(&&)连起来的量中,取第一个广义`false`(没有则取最后一个量), + +若干个由“或”(||)连起来的量中,取第一个不是广义`false`的量(没有则同上), + +若干个由“异或”(^)连起来的`true`或`false`,总结果取决于其中`true`的个数的奇偶,奇数个`true`则总结果为1,偶数个`true`则总结果为0. + +所以样板中充斥着形如`x=x||y`和`z=x||y`的代码,y算是x的默认值。 + +这种做法并不安全,如果您的作品不打算发布到我们的`h5mota.com`,则更建议使用`x??y`,它只会在“x为`null`或`undefined`时”才用y代替x。 + +双问号运算在早期浏览器并不被支持,因此如需在我们的站点发布则需要写`if(x==null){x=y;}`。 + +再次强调,广义`false`不一定和`false`弱相等(比如`null`),而和`false`弱相等的也不一定是广义`false`(如`[]==![]`)。 + +V2.7.3起,一元运算块追加了一些数学函数,如四种取整、开平方、绝对值。还追加了“变量类型”(typeof),只有两个类型相同的变量才有可能满足“相等”(===)。 + +V2.8起,二元运算块追加了一些js函数,如“取较大/较小”(`Math.max`和`Math.min`)、“开始于/结束于”(字符串的`startsWith`和`endsWith`)、“包含”(数组和字符串的`includes`),进阶作者可以学习使用。 +### 只读块(怪物属性、图块ID、图块类别、装备孔) + +尽管这几个值块是只读的,但您仍然可以使用“设置怪物属性”、“穿脱装备”和“转变图块”等指令去改变它们。 + +1. **怪物属性:**冒号缩写量写作`怪物:绿头怪:生命`,json代码中写作`enemy:greenSlime:hp`。怪物名称有重复的话可以在事件编辑器顶部关闭中文替换功能,道具名称同理。 + * 常见的怪物属性都提供在了下拉框中,如果下拉框里没有(如通过配置表格新增的属性),可以修改`_server\MotaAction.g4`文件最后的那些表来追加其中文简称,也可以放弃解析回中文块而是就用缩写量。 + * V2.8起,怪物支持“行走图朝向”绑定,此值块将始终获取脸朝下的怪物属性。 +2. **图块ID:**冒号缩写量写作`图块ID:x,y`,json代码中写作`blockId:x,y`。请注意逗号始终要用英文的,此值块实际直接调用的API为`core.getBlockId(x,y)`,即本值块和对应的冒号缩写量只支持本层,可以获知本层某个点的图块ID. +3. **图块数字:**冒号缩写量写作`图块数字:x,y`,json代码中写作`blockNumber:x,y`。同上,实际调用`core.getBlockNumber(x,y)`,可以获知本层某个点的图块数字。 +4. **图块类别:**冒号缩写量写作`图块类别:x,y`,json代码中写作`blockCls:x,y`。同上,实际调用`core.getBlockCls(x,y)`,可以获知本层某个点的图块类别。 + * V2.8起,这三个值块中的坐标支持填写表达式,会在json区直接翻译为API调用,但这样填写也意味着无法再解析回值块,而且本质上这已经不是冒号缩写量了,不能直接用于${表达式计算}等场合。 +5. **第n格装备孔:**冒号缩写量写作`装备孔:n`,json代码中写作`equip:n`。实际调用`core.getEquip(n)`,可以获知勇士第n个装备孔中的装备ID,n从0开始! + +### 可读写块(status、item、flag、switch、temp、global、buff) + +比起只读块,可读写块允许作为“数值操作”指令的左块: +1. **状态:**冒号缩写量为`状态:生命`等,json代码中写作`status:hp`等。 + * 作为左块时会调用`core.setStatus()`,其他时候会调用`core.getStatus()` +2. **物品:**冒号缩写量为`物品:炸弹`等,json代码中写作`item:bomb`等 + * 作为左块时会调用`core.getItem()`或`core.setItem()`,其他时候会调用`core.itemCount()`来统计背包中的道具数量。 + * 此外,最常用的一些勇士状态(包括坐标和朝向)、三色钥匙、三围增益都提供在了下拉框中。您可以修改`_server/MotaAction.g4`文件最后的`FixedId_List`来追加,向“楼层转换”、“门信息”和“装备属性”的下拉框追加新的楼梯ID、钥匙ID和勇士状态名也是一样的道理,当然不追加也不影响手写(如果指令只提供了下拉框版本,那就必须写在json区再解析)。 +3. **变量:**冒号缩写量为`变量:hatred`等,json代码中写作`flag:hatred`等。 + * 作为左块时会调用`core.setFlag()`,其他时候会调用`core.getFlag(’xxx’, 0)` + * 请注意:变量类型支持中文,如`变量:开门个数` + * 这两个API实际操作的就是前文多次提到的`core.status.hero.flags`,只不过在事件中未定义的变量都视为0,更为安全。 + * 如果您忘记了自己在哪些事件用过哪些变量,请善用事件编辑器顶部的“搜索变量出现位置”。 +4. **独立开关:**如果您有一大批NPC都具有“首次对话不同于之后各次对话”之类的特点,那么为他们设置一大批不重名的变量就很繁琐。独立开关(叫独立变量更准确)就是为这种场合而生的,它对每层楼(首次到达和每次到达中)和每个坐标都是独立的。 + * 冒号缩写量写作`独立开关:A—Z`,json代码中写作`switch:A—Z`。 + * 每个坐标处的独立开关有26个,用大写拉丁字母A—Z表示。MTn层(x,y)点的独立开关A,本质上是一个flag变量——`flag:MTn@x@y@A`,如果需要在其他点访问,就需要用这样的写法。 + * 首次到达/每次到达楼层的事件中,上述x和y会保留小写字母而不代入任何数字。 + * 标题事件/开场剧情中,前缀的“楼层ID”视为“`:f`”。 + * 可以在事件编辑器左侧菜单的“常用事件模板”中看到一个仿51层/新新魔塔的一次性商人的例子。 +5. **临时变量:**冒号缩写量写作“临时变量:A—Z”,json代码中写作`temp:A-Z`, + * 临时变量泛指所有以`@temp@`开头的flag变量,一共也有26个。 + * 临时变量一般用于计数循环和数组迭代,每当事件流结束(勇士恢复行动)时,临时变量和参数变量(以arg+纯数字命名)都会被清空。 + * 全局商店状态下,临时变量`@temp@shop`会被启用,从而实现长按连续购买等操作。 + * 上面提到的“一次性商人”模板中多次出现出售数量和价格常数,如果使用临时变量就很方便,不需要修改多个地方了。 +6. **全局存储:**冒号缩写量写作`全局存储:xxx`,json代码中写作`global:xxx`, + * 和变量一样,冒号右侧本身支持中文。全局存储一般用来保存一些跨存档的信息,如成就系统、回想和音像鉴赏的收集进度、多周目数据等。 + * 用于左块时会调用`core.setGlobal()`,其他时候会调用`core.getGlobal()`。 + * `core.setGlobal()`在录像中会被忽略,`core.getGlobal()`在正常游戏中会将读取到的值写进录像,而在录像播放中直接读取这个写进去的值,从而复原了那次游戏时的样子。 + * 然而,由于`core.getGlobal()`的调用频率在正常游戏和录像播放中可能不一致,因而可能会导致录像中的记录次数过多、过少或位置不合适。 + * V2.8对这个问题进行了一定的修复,请放心使用。 +7. **增益:**冒号缩写量写作`增益:生命上限`等,json代码中写作`buff:hpmax`等,冒号右侧支持自定义的勇士新属性英文名。 + * 用于左块时会调用`core.setBuff()`,其他时候会调用`core.getBuff()`。 + * 增益这个概念在前面讲解装备属性时提到过,所有的百分比装备和百分比衰弱都是靠它发挥作用。 + * `buff:xxx`本质上就是`flag:__buff_xxx__`,但前者更安全(默认值为1)。 + +## 随机数(扩展内容,了解即可) + +此外,V2.8还新增了几个样板API的值块,包括“当前是否身穿某装备”、“当前是否在录像播放中”、“是否到达过某楼层”、“是否开启了某全局商店”、“能否打败某怪物”、“勇士面前第n格的坐标(负数表示身后)”、“随机数”。 + +样板包括两种随机数,一种是上面说的值块提供的`core.rand(n)`,会得到小于正整数n的随机自然数,n不填则会得到小于1的随机正小数。 + +一种则是`core.rand2(n)`,同样会得到小于n的随机自然数,n必须填正整数,不要省略! + +rand本质上是`flag:__rand__`不断一步步推进的结果,初始值为`flag:__seed__`,初始值(又叫随机种子)和游戏难度可以在标题画面通过`core.startGame(hard,seed)`函数指定(如果开启了标题界面事件化,则该函数还必须作为游戏重启函数`core.hideStartAnimate()`的回调来使用,但这样只能指定种子),种子必须为正整数。 + +rand2则是通过js自带的`Math.random()`函数得到的真随机小数经过整数化处理的结果,会被直接写入录像(`random:n`),因此多次调用会导致录像(和存档)变得很庞大。而且正因为是写入录像,所以玩家也可以直接编辑录像文件或`core.status.route`来控制随机的结果。 + +直观地说,rand的结果只和它的调用次数有关,因此玩家不能通过简单的SL操作来优化结果,必须调整路线或干脆重开游戏。 + +而rand2的结果可以通过SL来改变,如果需要连续大量使用“可被SL改变的随机数”(期间不能存档)但又不希望录像变得太庞大,可以先在存档后使用一次`n=core.rand2(114514)`,再使用n次`core.rand()`来推进,效果就能让人满意了。(新新魔塔1就是这样的思路,每次撞怪时先自动存档然后生成n值,战斗中只使用rand)。 + +然而,如果rand和rand2的使用场合不当,那么其调用频率在正常游戏中和录像回放时可能不一致。rand会因为推进步数不一致而导致游戏流程发生变化,rand2则会导致录像中记录的随机数出现多余或缺失。 + +V2.8对rand2(以及“全局存储”、“弹窗输入”、“选择项”、“确认框”)的此问题进行了一定的修复,如果录像中出现了多余的随机数记录,就会在播放时跳过并在控制台警告,如果需要rand2但未记录或记录越界则会现场重新随机生成并补录。在V2.8之前这两种情况都会直接报错,而在V2.8中会导致首次播放结果的二次记录不一致,请不要惊慌,按R键从头再次播放一遍即可。 + +总之,不计入录像的纯观赏性的随机效果(如捡到某道具随机播放一个音效甚至随机指定音调高低,以及样板雨雪效果中的随机)建议直接用`Math.random()`实现(例如V2.8每次上下楼后会在楼层属性的背景音乐中随机播放一个)。 + +## 录像回放(扩展内容,了解即可) + +魔塔具有时空离散性和完全可重复性,因此可以像棋类运动一样记录类似棋谱的东西,我们称之为【录像】。 + +正常游戏中,已录制的内容被保存在一维数组`core.status.route`中并不断从尾部延长,录像回放时,即将播放的内容会保存在一维数组`core.status.replay.toReplay`中并不断从头部缩减。 + +V2.8.1起,录像中勇士死亡将会报错并询问是否回到上一个节点,比起只能读取自动存档更为友好。 + +您可以在正常游戏中自由行动时随时按下R键进行回放,上述数组具体的内容含义如下: + +``` +up down left right 勇士向某个方向行走一步 +item:ID 打开背包使用某件道具,如item:bomb表示使用炸弹 +unEquip:n 卸掉身上第n件装备(n从0开始),如unEquip:1默认表示卸掉盾牌 +equip:ID 打开背包穿上一件装备,如equip:sword1表示装上铁剑 +saveEquip:n 将身上的当前套装保存到第n套快捷套装(n从0开始) +loadEquip:n 快捷换上之前保存好的第n套套装 +fly:ID 使用楼传飞到某一层,如fly:MT10表示飞到主塔10层 +choices:none 确认框/选择项界面超时(作者未设置超时时间则此项视为缺失) +choices:n 确认框/选择项界面选择第n项(选择项中n从0开始,确认框界面0表示确定,1表示取消),此项越界将报错。此项缺失的话,确认框将选择作者指定的默认项(初始光标位置),选择项将弹窗请求补选(后台录像验证中则选默认项)。 +shop:ID 打开某个全局商店,如shop:itemShop表示打开道具商店。因此连载塔千万不要中途修改商店ID! +turn 单击勇士(Z键)转身 +turn:dir 勇士转向某个方向,dir可以为up down left right,注意此项在正常游戏中无法随意触发。 +getNext 轻按获得身边道具,优先获得面前的,身边如果没有道具则此项会被跳过。 +input:none “等待用户操作事件”中超时(作者未设置超时时间则此项会导致报错) +input:xxx 可能表示“等待用户操作事件”的一个操作,也可能表示一个“接受用户输入数字”的输入,后者的情况下xxx为输入的数字。此项缺失的话前者将直接报错,后者将用0代替 +input2:xxx 可能表示“读取全局存储(core.getGlobal)”读取到的值,也可能表示一个“接受用户输入文本”的输入,两种情况下xxx都为base64编码。此项缺失的话前者将重新现场读取,后者将用空字符串代替 +no 走到可穿透的楼梯上不触发楼层切换事件,注意正常游戏中勇士无法随意停在旁边没有障碍物的楼梯上。 +move:x:y 尝试瞬移到[x,y]点,注意正常游戏中勇士无法随意瞬移到相邻格,而回放时连续的瞬移操作将被合并。 +key:n 按下键值为n的键,如key:49表示按下大键盘数字键1,默认会触发使用破墙镐 +click:n:px:py 点击自绘状态栏,n为0表示横屏1表示竖屏,[px,py]为点击的像素坐标 +random:n 生成了随机数n,即core.rand2(num)的返回结果,n必须在[0,num-1]范围,num必须为正整数。此项缺失或越界将导致现场重新随机生成数值,可能导致回放结果不一致! +作者自定义的新项(一般为js对象,可以先JSON.stringify()再core.encodeBase64()得到纯英文数字的内容)需要用(半角圆括弧)括起来。 +``` + +[在线插件库](https://h5mota.com/plugins/)中提供了“录像自助精修”插件,手机也适用,可供研究。 + +开门打怪前会先转身再自动存档,因此该存档被读取后,已录制内容的最后一步将变成转身,导致录像变长,请自行注意。 + +========================================================================================== + +[继续阅读下一章:事件指令](instruction) diff --git a/_docs/index.html b/_docs/index.html new file mode 100644 index 0000000..2711059 --- /dev/null +++ b/_docs/index.html @@ -0,0 +1,197 @@ + + + + + HTML5魔塔样板 + + + + + + + + + + + +
      + +
      + + + + + + + + diff --git a/_docs/index.md b/_docs/index.md new file mode 100644 index 0000000..30af938 --- /dev/null +++ b/_docs/index.md @@ -0,0 +1,25 @@ +# HTML5 魔塔样板说明文档 + +当您打开这份帮助文档的瞬间,相信您一定是抱着幼年时的游戏开发梦想前来的。众所周知,即时游戏的开发要比非即时游戏难上许多,像素级游戏的开发又要比网格地图游戏难上许多。 + +在非即时网格地图游戏(譬如策略战棋)中,有一类叫做“固定数值RPG”,简称“魔塔”。这是一种基于运筹学的数学优化建模游戏,虽然小众,却不失有自己的圈子。 + +在当下,魔塔的趋势是向移动端发展,网络上也常常能见到“求手机魔塔”的提问。然而现有的工具中,NekoRPG有着比较大的局限性,游戏感较差,更是完全没法在iOS运行。而一些APP的魔塔虽然可用,但是必须要下载安装,对于安卓和苹果还必须开发不同的版本,非常麻烦。 + +但是,现在我们有了HTML5。 +HTML5的画布(canvas)以及它被Android/iOS内置浏览器所支持的特性,可以让我们做出真正意义上的全平台覆盖的魔塔。 + +然而,一般而言使用非RPG +Maker制作魔塔往往需要一定的编程技术,HTML5魔塔自然也不例外。但是,为了能让大家更加注重于“做塔”本身,而不用考虑做塔以外的各种脚本问题,@艾之葵(GitHub +ckcz123)特意制作了这样一部HTML5魔塔样板。 + +这个魔塔样板,可以让你在完全不懂任何编程语言的情况下,做出自己的H5魔塔。不会代码?没关系!只要你想做,就能做出来! + +继续查看文档的详细介绍,让你学会如何使用这一个样板来制作属于自己的HTML5魔塔,或者……任何非即时的网格地图游戏。 + +* [新版视频教程](https://www.bilibili.com/video/BV1SB4y1p7bg?share_source=copy_web) +* [脚本教程](https://www.bilibili.com/video/BV1uL411J7yZ?share_source=copy_web) + +========================================================================================== + +[继续阅读下一章:现在就做出自己的第一部H5魔塔!](start) diff --git a/_docs/instruction.md b/_docs/instruction.md new file mode 100644 index 0000000..8d364ec --- /dev/null +++ b/_docs/instruction.md @@ -0,0 +1,656 @@ +# 事件指令 + +?> 在这一节中,让我们来了解每一类事件的具体介绍 + +本样板之所以敢宣称“零编程基础的您也能大展身手”,就是因为它搭载了强大的图形化json编辑器(blockly)。 + +熟悉Antlr语法的读者可以根据[修改编辑器](editor)去自行修改`_server\MotaAction.g4`等文件去扩展其功能。 + +json代码本身则可以作为`core.insertAction()`函数的自变量,去插入一段事件执行。 + +下述提到的“当前点”坐标均指`core.status.event.data.x`和`core.status.event.data.y`。 + +## 指令的分类(注意区分块的颜色和地图的七彩点) + +尽管事件指令和`core.events._action_xxx()`函数是一一对应的,但它们进一步调用的底层函数却分布在libs文件夹的不同文件中,大致上: + +* 显示文字类(严格来说需要玩家操作的事件还涉及到actions.js)和UI绘制类在ui.js +* 数据相关类(这类也有不少道具相关在items.js)和特效声音类在control.js +* 地图处理类在maps.js +* 事件控制类(或许应该叫流程控制类)则在events.js,请注意区分libs和project的同名文件。 + +另一种分类方法则是按照同步和异步,分成以下几类: +1. **瞬间就能执行完的:**如UI绘制、设置XX属性、显隐和转变图层块等。 +2. **阻塞直到玩家操作的:**如显示文章/选择项/确认框、接受用户输入、等待用户操作、呼出xxx等。 +3. **阻塞一段固定时间的:**如开关门、显示动画(观赏性)、移动跳跃、淡入淡出等。 +4. **耗时但不阻塞的:**如播放音效(V2.8支持阻塞)、显示提示等,一般为纯观赏性指令,会和后面的指令同时执行。 + +上述第3类指令都可以勾选“不等待执行完毕”(即前面提到的异步事件)变为第4类,从而实现诸如同步开关多个门的效果。 + +在json区,每条指令的格式为:`{"type": "xxx", "param1": ..., "param2": ..., ......}`, +实际执行的函数为`core.events._action_xxx(data, x, y, prefix)` + +data为整个指令对象,x和y为当前点坐标,prefix为独立开关的楼层前缀。 + +您可以自行使用`core.registerEvent`注册一个新的事件。如果需要把新指令做成像已有的指令一样有类别、名称、取色器、勾选框、下拉框、输入框等部件,请查阅[修改编辑器](editor)。 + +V2.8.1起,下拉框中没有的项都可以通过在json区输入并点击“解析”按钮来临时追加(刷新网页后失效),如需永久追加请查阅[修改编辑器](editor)。 + +与此同时,显示文章、显示选择项、显示确认框都支持双击预览,预览前请先摆一个“设置剧情文本的属性”设置您预览时需要的属性然后双击它。 + +另外,原本“显示文字类”的图片相关指令,和“特效声音类”的音频相关指令,在V2.8.1被移出来合并到了一个新类“音像处理类”,请知悉。 + +## 显示文字类(黄色) + +![image](img/images_texty.jpg) + +这个类别的指令会负责UI层图文的处理,如图片的移动和淡入淡出,游戏的胜败和重启等。 + +### 显示文章 + +最基本的就是最灵活的。本指令的讲解将占用大量篇幅,请做好准备。 + +上述函数中,第一个自变量为字符串数组或单个字符串,每个字符串为一段需要玩家按下确认键或点击屏幕才会消失的剧情文本,第二个自变量(可选)为全部文本消失后的回调函数。 + +每条显示文章分为五部分:标题、图像、对话框效果、正文、立绘。 + +写成一个字符串就是`\t[标题,图像]\b[对话框效果]\f[立绘]正文`。 + +1. **标题:**可选,一般填说话人的名字。如果不填,则尝试根据图像取中文名(道具除外,道具不会说话所以只填图像不填标题就会没有标题)。如图像填hero但不填标题则以勇士名字作为标题,标题还可以填`null`强制不显示标题(如`\t[null,hero]`只显示勇士头像)。 +2. **图像:**可选,可以填hero(勇士行走图,如果勇士开了帧动画则会取当前朝向,但朝上会视为朝下)或任何图块ID,或者填this来尝试取当前点图块。 + * 也可以填一个png格式的图片文件名(需要后缀),图像甚至还可以填null来避免以图块ID为标题被解释成图像(如`\t[bomb,null]`会以英文单词bomb为标题而没有图像,但单独的`\t[bomb]`则会没有标题但以炸弹道具为图像)。 + * 只填写图像而不填写标题时,会被优先解析到标题框中,请不要惊慌,这并不影响效果。 +3. **对话框效果:**可选,填法非常灵活,如下(坐标在大地图中均为绝对坐标,省略则取当前点)。 + 1. `up,x,y`:对话框显示在点(x,y)上方,尖角朝下指着这个点(具体指的高度取决于图像。没有图像则判断该点是32×32px还是32×48px的图块,但是不管有无图像,若该点没有图块则都没有尖角)。 + 2. `down,x,y`:对话框显示在点(x,y)下方,尖角朝上指着这个点。比起上面,这个没有高度问题,不过该点没有图块的话则还是没有尖角。 + * 上述两种写法中,如果把坐标换成hero则显示在勇士上方或下方(尖角朝下的话,高度取决于勇士的行走图)。 + 3. `this,x,y`:在大地图中,点(x,y)可能位于视野上半部分也可能位于下半部分,写this就能让对话框自动适配上下来防止越界。 + 4. `hero`:在大地图上下边缘或小地图,勇士可能位于视野上半部分也可能位于下半部分,只写hero也能自动适配。 + 5. `hero,n`:n为正整数,尖角指向勇士的第n名跟随者,自动适配上下,将hero改为up或down则手动指定上下。 + 6. `up,null`:显示在屏幕最上方,同理up换成down则为最下方。 + 7. `center`:强制显示在屏幕中央,宽度为视野宽度。 + 8. 除最后两种外,其余写法都会给对话框进行宽度自适配: + * 如果正文没有自动换行,则会先尝试取一个让文本总行数最接近“几行半”的宽度,可能会再适当加宽来防止标题出界。 + * 如果正文有自动换行,则会尝试连同标题在内取最长的一行作为宽度。 + * V2.7起,文本绘制一定程度上支持了排版标点禁则,成对符号的左半部分不会出现在行尾,右半部分和其他某些标点不会出现在行首。 + 9. 最终绘制时会尽量让尖角居中,除非尖角所指的点实在太靠左或太靠右。 + 10. 值得注意的是,使用`project/images/winskin.png`或类似的图片作为文章背景时,尖角的绘制用的是用图片右下角64×32px的两格进行的,所以往往需要您自己准备好。 + * 技术群`959329661`的群文件“常用素材”提供了一些已经制作好的这样的图片,敬请取用。 +4. **正文:**双击指令块,进入多行编辑。正文中允许使用很多转义序列,当您键入一个\字符时就会提示补全,后面逐一介绍。 +5. **立绘:**显示文章的同时可以绘制一张或多张立绘图,请双击预览各张图的效果或右击整个指令预览所有立绘。每张立绘由一大堆参数组成:`\f[name(:x/:y/:o,sx,sy,sw,sh,)x,y(,w,h,alpha,angle)]` + 1. **文件名:**需要放在project\images文件夹中并注册,这里需要带后缀。 + 2. **翻转:**和楼层贴图一样,支持三种翻转,在json代码中以文件的后缀名之后追加“:x、:y、:o”来表示。 + 3. **绘制坐标:**立绘在视野中的左上角像素坐标,后面的参数一旦省略其中一个则必须省略其后所有。 + 4. **绘制的宽高:**立绘被伸缩后在视野中实际显示的宽高,必须同时填写,不填则不伸缩。 + 5. **裁剪坐标和宽高:**必须同时填写,为从原图中裁剪区域的左上角坐标和区域宽高,不填则取全图。 + 6. **不透明度和旋转角度:**可选,前者填一个不大于1的正数,请自行双击预览。 +6. **左上角坐标、限宽:**V2.8.1新增,该项不能和上面的`\b[对话框效果]`同时使用。 + * 使用此项后,本指令将变为正常的json格式而不是字符串格式。 + * “左上角坐标”指视野中的相对像素坐标,“限宽”必须和左上角坐标一起指定,不能单独指定。 + * “限宽”的下限,在没有图像的情况下大约为64px,在有图像的情况下大约为128px,可以双击预览来确认到底能否正常显示。 + * 一种推荐的用法是,在指定下面的非0编号后使用,然后“显示选择项/确认框”,这样就和RPG Maker的行为一致了。 + * 而且还能充分利用两侧的空间讲清楚每个子选项,这是在“选择项的提示文字无法随光标位置而变化”的现状下的一种妥协做法。 +7. **编号:**V2.8.1新增,可以同时显示多个不同编号的对话框,常用来表现多名角色的嘈杂对话。 + * 和上一项一样,此项填写非0值后,指令将变为正常的json格式而不是字符串格式。 + * 非0编号的对话框将不会自动消失(甚至勇士恢复自由行动后也是),可以被同编号的对话框覆盖或手动清除。 + * 自由行动时,该项常用来做一些常驻提示,如技能的开关状态、毒衰咒状态、当前剧情任务进度等。 + * 您甚至可以像图片一样去移动对话框(如和一个npc一起移动),支持四种变速效果,立绘也会被一起移动。 + +立绘是画在UI层的,下一个指令执行前就会擦除。如需持续显示请使用“显示图片”指令,另外立绘会被“图像”遮挡。 + +### 显示文章正文的转义序列 + +1. **表达式计算:**使用`${}`可以计算(eval)一个js表达式,式子中允许使用所有的冒号缩写量和API,详见`core.calValue()`函数。 + * 此语法也可以用于“道具名称”、“道具描述”和“即捡即用提示”,只不过那里就不支持中文替换了。 + * 如`勇士当前的攻防相乘是\${状态:攻击\*状态:防御}`(中文替换),`持有三色钥匙共\${item:yellowKey+item:blueKey+item:redKey}把。`(json) + * V2.8和更早的版本中,样板对右花括弧的处理是(非贪心)正则匹配,因此`${内部}`不能再出现右花括弧,这也意味着您无法使用对象、复杂的流程控制语句和函数定义,只能使用简单的运算符(包括三元运算)和函数调用。 + * V2.8.1中,匹配方式改为了堆栈匹配,上述问题得到解决,您可以放心使用匿名函数了,这对道具名称/道具描述这种场合是个福音。 +2. **局部文字变色:**使用`\r[颜色英文名]`或`\r[\#RrGgBb]`(十六进制)来将这之后的文本变为另一种颜色。 + * 最常用的17种颜色提供了自动补全,十六进制颜色可以随便找个有颜色参数的指令,呼出调色器去自己调配。只使用\r不带方括号则变回默认颜色。 +3. **局部字号调节:**使用`\\c[正整数]`改变这之后文本的字号,只使用`\\c`不加方括号则恢复默认字号。 +4. **手动换行、局部加粗和斜体:**退出多行编辑后,手动换行写作`\n`,另外可以使用`\\d`将局部文本加粗或取消加粗,使用`\\e`将局部文本变为斜体或取消斜体。 +5. **32×32px图标的绘制:**使用`\\i[图标ID]`绘制一个32×32px的图块的第一帧或系统图标,您可以使用`core.statusBar.icons`查看所有的系统图标。 + * 出于历史遗留问题,图块ID可以和系统图标ID重复,此时优先使用图块ID. +6. **打字速度调节:**开启打字机效果后,文本的打字速度总是匀速的。所以样板提供了名为“时间占位符”的转义序列,使用`\\z[正整数]`可以暂停相当于打这么多个字的时间。 + +除`\n,\t,\b,\r,\f`外,其余转义序列的反斜杠在json中必须写两个! + +### 其他文字类指令 + +1. **自动剧情文本:**和上面的显示文章基本相同,只不过不是由玩家按下确认键或点击屏幕,而是一定毫秒后自动消失,录像回放中则忽略。 + * 比起那个,这个不能通过长按Ctrl键或长按屏幕快进,大量使用时一般用来搭配语音。否则对魔塔这种快餐游戏来说可能会非常不友好,建议统一塞进“显示确认框”指令的场合之一。 +2. **滚动剧情文本:**将一段文字从屏幕最下方滚动到最上方,不支持自动换行,常用于op和ed. + * 该指令对应`core.drawScrollText(content, time, lineHeight, callback)`。 +3. **显示提示:**即诸如打败怪物、捡到道具、打不开门时左上角的提示,只支持`${表达式计算}`和`\r[变色]`。 + * 可以填写一个图标ID显示在提示文本的左侧(支持32×48px但只画靠上的2/3,也可以使用系统图标)。 + * 此指令对应`core.drawTip(text, icon, frame)`函数。自然数`frame`表示绘制第几帧,默认为0表示第一帧(但没有在事件中提供)。 + * 此指令看似显示出的提示会过一会才消失,但此指令本身依然是瞬间完成的,不属于异步事件! + * V2.6.4短暂提供了“同时显示多个tip”的功能,这在事件中连续获得多个道具时很友好, + * 但是玩家纷纷表示自由行动时此功能遮挡了太多的视野,因而在V2.6.5中就取消了。 + * 如果需要此功能(包括自定义底色),请在本项目的[github](https://github.com/ckcz123/mota-js/archive/refs/tags/v2.6.4-release.zip)站点自行下载V2.6.4的发布版本并对照研究。 +4. **游戏胜败和重启:**游戏胜败分别对应“脚本编辑”(快捷键N)的`win`和`lose`函数,在线游戏排行榜中每个结局的每个难度都有一张榜。 + * 但同一结局只有最高难度有效,您可以勾选“不计入榜单”来让这个本来有效的结局也无效。还可以勾选“不结束游戏”来先停止录像的录制,再演出ed. + * win和lose函数最终都会调用`core.gameOver(ending)`函数,区别在于lose不填写ending. 但是,事件中使用gameOver这一异步函数需要额外的技巧,详见“原生脚本”。 + * 重启游戏对应的函数为`core.showStartAnimate()` +5. **设置剧情文本的属性:**可用`core.status.textAttribute`获取当前的剧情文本属性,各项含义: + 1. **位置:**“显示文章”不使用`\b`对话框效果时文本的位置,默认为屏幕中央。如果您有大量集中的剧情文本都欲使用`up,null`的对话框效果,则可以直接将此项设置为“顶部”,而将剩余的个别剧情文本使用`center`或`down,null`的对话框效果,反之亦然。 + 2. **偏移像素:**上面的“位置”选择“顶部”或“底部”时和屏幕上下边缘的距离,也作为滚动剧情文本和左边缘的距离。 + 3. **对齐:**默认为左对齐,可以修改此项来让显示文章的标题和正文都居中或都右对齐。 + 4. **标题色:**准确地说是“标题和图像边框色”,格式和楼层画面色调一致,可以点击调色器按钮呼出调色器调色。 + 5. **正文色:**如题,修改方法同上,从而避免频繁使用\r进行局部文本变色。 + 6. **背景色:**如题,修改方法同上。但比起前两个,这个也允许填写一个类似`winskin.png`的图片文件名。 + 7. **标题和正文字号:**如题,正文字号推荐设置为偶数。 + 8. **行距和字符间距:**如题,单位都是像素,行距推荐为正文字号的一倍半。 + 9. **粗体(Y/N):**文本是否默认加粗,推荐在大量粗体文本中穿插少量细体文本时使用,以免频繁的`\\d`切换。 + 10. **打字间隔:**0表示不启用打字机效果而是一次显示完,正整数表示每打一个字的毫秒数,也作为`\\z`暂停的单位时间。 + 11. **淡入淡出时间:**V2.8新增,指定此项后,每个“显示文章”指令都会增加淡入淡出效果,建议用于大段的剧情层。 + +可以使用`core.clone(core.status.textAttribute)`将文本属性备份到`hero.flags`中,从而做到临时使用另一套文本属性绘制部分内容。 + +### 图片类指令 + +1. **显示图片:**和立绘的语法基本类似,只不过多了编号(1—50)和淡入时间。 + * 可以双击预览效果,还可以勾选“不等待执行完毕”来和后面的指令同时执行,比如同时淡入两张图片,或者淡入一张同时淡出/移动另一张。 + * 编号较大的图片会遮盖较小的,1—24号图片会被色调层遮盖,25—40号图片会遮盖色调层但被UI层遮盖,41—50号图片会遮盖UI层。 + * 此指令对应`core.showImage()`函数,编号真正的意义,详见[个性化](personalization) +2. **清除图片:**如题,需要指定要清除的图片编号和淡出时间(显隐图片的时间都可以填0表示瞬间完成)。 + * 此指令对应`core.hideImage(code, time, callback)` +3. **图片移动:**其实还包括了透明度渐变,“终点像素位置”指移动结束后的图片在视野中的左上角像素坐标(不填则表示单纯的透明度渐变),“不透明度”指渐变结束后的新的不透明度(不填表示单纯的移动)。对应`core.moveImage(code, to, opacityVal, moveMode, time, callback)` + * V2.8起,图片和视野的移动支持加速度,分为“匀速、加速、减速、先加速再减速”四种,请任意选用。 +4. **图片旋转:**V2.8新增,同样支持加速度,旋转中心坐标不填则取图片中心。 + * 此指令对应`core.rotateImage(code, center, angle, moveMode, time, callback)`函数。 + * 比起移动,旋转本身不支持同时透明度渐变,您可以先写一个不指定终点的移动指令且“不等待执行完毕”来实现单纯的淡入淡出,然后再写一个耗时相同或略长的旋转指令,这样两个指令就会一起执行了。 + * 当不指定旋转中心时,本指令可以和移动指令同时使用,从而得到“图片的中心做直线运动、同时图片还在绕着中心自转”的效果。 +5. **图片放缩:**V2.8.1新增,同样支持加速度,放缩中心坐标不填则取图片中心。 + * 此指令对应`core.scaleImage(code, center, scale, moveMode, time, callback)`函数。 + * 可以和“图片移动/旋转”一起使用,做出PowerPoint中常见的动画效果。 +5. **显示或清除动图:**需要填写动图的文件名(带.gif后缀),“起点像素位置”含义如前且必须填写,可以双击指令块来预览第一帧的效果。 + * 动图不支持淡入淡出和伸缩移动,如果不填任何参数则清除所有动图(只支持全部清除)。 + * 该指令对应`core.showGif(name, x, y)`函数。 +6. **显示图片化文本:**这是您唯一显示镜像文字的机会。 + * 显示出来后就会视为一张通常的图片,可以被清除、移动、淡入淡出、旋转。 + +### 显示确认框,选择项,QTE与全局商店 + +QTE,即快速反应事件。一般表现为需要玩家在收到某信号后尽快做出正确操作,如新新魔塔2中面对白银史莱姆王的猜拳战斗就需要根据其出拳的颜色尽快按下相克的数字键。 + +样板同样支持这类事件,一共有三种,这里先介绍两种。 + +一是**显示确认框**,它会显示一段支持`${表达式求值}`但不支持自动换行、淡入淡出和其他转义序列的文字。然后要求玩家在一定毫秒数内选择“确定”或“取消”之一,如果超时就视为哪个都没选,直接继续执行后面的事件。 + +您可以指定闪烁光标的初始停留位置是确定还是取消,还可以指定超时毫秒数为0表示不限时间但玩家必须做出二选一。 + +当指定了超时时间并且及时做出选择时,剩余时间会被写入“变量:timeout”,可以用来做一些处理(音游?)。 + +此指令对应`core.drawConfirmBox(text, yesCallback, noCallback)`函数,其中两个Callback分别为选择确定和取消后的回调函数。 + +V2.8起,显示确认框在录像回放时如果录像中没有记录该选哪一项(或者明明此事件未设置超时时间但录像中记录了超时),就会选择默认项(也就是光标的默认位置),请注意。 + +二是**显示选择项**,和RPG Maker不同,我们的选择项不会和它之前的“显示文章”同时出现,可以直接配上除对话框、打字机、淡入淡出外的所有文字效果。 + +此指令对应`core.drawChoices(content, choices)`函数,其中content为提示文字,choices为各子选项文字组成的字符串数组。是的,比起上面的函数,这个不直接支持回调。 + +在没有提示文字的情况下,一次能同时显示的子选项最多为13或15个。和确认框一样,选择项的超时值填0表示不限时间但玩家必须从中选择一个。大于0的话超时视为什么都没选,直接继续执行后面的事件。 + +每个子选项的文字只支持`${表达式求值}`和整行变色,请注意控制字数。文字左侧也支持追加一个图标(多帧图块取第一帧),支持系统图标。 + +每个子选项还可以指定“出现条件”(不指定则一定出现),条件的写法和自动事件的触发条件一样,从而做出形如“怒气值满才显示大招选项”的效果。 + +如果实际执行时所有子选项都不满足出现条件,则直接跳过。但是如果出现的项都不满足下面的“启用条件”就会导致所有项都不可选,然后游戏卡死,请务必注意这个问题。 + +V2.7.2起,每个子选项还可以指定“启用条件”,当出现条件和启用条件都满足时才能真正选中这一项。 + +如果只满足前者但不满足后者,该项会变灰,尝试选择时会播放“操作失败”系统音效并显示提示(超时倒计时不会重置)。 + +当指定了超时时间并且及时做出有效选择时,剩余时间同样会被写入“变量:timeout”,可以用来做一些处理。 + +您或许会疑惑提示文字为什么不做成随光标位置而变化(这在很多电脑/手柄游戏中很常见),这是因为触屏设备无法改变光标位置,如有需要,请自行在此界面提供虚拟方向键(不打算发布触屏版游戏的则无所谓),然后在提示文字中使用${}对`core.status.event.selection`进行判定,从而改变提示文字。 + +V2.8起,显示选择项在录像回放时如果录像中没有记录该选哪一项(或者明明此事件未设置超时时间但录像中记录了超时),就会弹窗询问玩家是否补选一项,此时玩家可以输入某项的序号(从0起)来修复录像,当然也可以在弹窗时点取消来放弃修复。 + +提交成绩后,站点后端的录像验证时如果发生了同样的问题,则因为无法询问玩家,会选择默认项(即初始光标位置,下面会提到)。 + +如果录像回放中尝试选择不满足“启用条件”的灰项,V2.8起会直接报错。这种处理方式比上面的“弹窗请求补选”更为严厉,如您在造塔测试时遇到了这种问题,可以先“回退到上一个节点”,然后在控制台输入`core.status.replay.toReplay`查看接下来该播放的内容(可以按N键单步播放),并在这个一维数组中删除掉那个非法的选项值。 + +而如果录像中记录了多余的确认框或选择项(`choices:n`),V2.8起就会在播放时跳过并警告。 + +V2.8.1起,“显示选择项”支持限宽和手动指定默认项了(注意最好指定一个100%出现且启用的项),配合“带编号的显示文章”效果更佳哦! + +![image](img/quickshops.jpg) + +在“全塔属性——全局商店”中可以编辑各个商店,商店一共有三种: + +1. **公共事件商店:**最简单的一种商店,或者应该叫做给玩家准备的快捷功能更合适,因为它的内容完全不一定非得是个做买卖做交易的“商店”,也可以是诸如“开启或关闭主动技能”、“快速换上最强套装”之类的便捷功能。 + * 多说一句,鉴于全局商店列表最多只能同时显示12或14项(还有一项是关闭列表),因此您也可以准备一些永久道具,设置适当的使用条件,并在使用效果事件中去实现这些给玩家的快捷功能。当然全局商店的使用条件更加统一,请自行权衡。 + * 公共事件商店在用法上和一般的“插入公共事件”并无二致,同样可以提供一个参数列表。 +2. **道具商店:**这种商店也很简单,由第三种QTE指令实现但没有限时。 + * 您可以在其中随意填写买卖的道具ID、存量、买卖价和出现条件。 + * 存量不填视为无限,买入价不填视为只能卖(回收),卖出价不填视为只能买,出现条件的含义和选择项一致。 + * 如果需要在游戏中对买卖价和对存量进行读写,请读写`core.status.shops` + * 请注意,使用道具商店的话务必保留`project/images/winskin.png`及其注册信息,可以换成相同规格的同名图片。 +3. **新版商店:**用法非常灵活的一种商店,其外形酷似“显示选择项”但有一些不同。 + * 首先和其他两种商店一样,它多出了“商店id、快捷名称、未开启不显示”。 + * 商店id只能使用全英数,且必须两两不同。 + * “快捷名称”为显示在V键快捷菜单的名称,请注意控制字数,最好也两两不同以免玩家混淆。 + * 勾选“未开启不显示”则此商店在开启前或禁用后不会出现在V键菜单中,当商店总个数超过12或14个且随着游戏流程进度依次开新的关旧的时,这个勾选项就很有必要了。 + * 其次,和其他两种商店不同,您可以允许玩家预览它(前提是V键菜单中显示了),这对魔塔这种倡导完全信息的游戏来说非常有意义。 + * 最后,比起常规的“显示选择项”,它不能指定超时毫秒数,但是(V2.8起)允许长按连续购买。 + * 实际执行中在所有子选项的最后会自动追加一个“离开”选项,选择其他子选项并执行后商店并不会立即关闭而是停在那个界面。就像胖老鼠和两部新新魔塔一样。 + * “出现条件”和“使用条件”相搭配,让您能够很轻松地做出形如“消耗金币和各种材料的装备合成路线”这样的设定。 + * 在预览模式下除“离开”外的子选项、以及交易模式下不满足“使用条件”的子选项,都会显示为灰色,尝试选择时会播放“操作失败”系统音效并提示失败原因。 + * 子选项的执行内容中需要手动处理扣费等问题,此外,在制作像两部新新魔塔一样会涨价的商店时,您也需要自己准备变量(变量名不必与商店id相同)去记录已购次数或者直接记录价格,并手动处理涨价问题。 + +有关全局商店的详细实现,请参见“插件编写”(句号键,`project/plugin.js`)。 + +其中还提供了一个`core.canUseQuickShop(id)`函数来控制勇士什么时候可以通过V键菜单快捷使用哪些商店,自变量id为商店id. + +本质上,新版商店是套在一个死循环里的。您可以在子选项的执行内容中使用“跳出当前循环”指令来打断该子选项剩余的未执行内容而强制离开商店, + +或使用“提前结束本轮循环”来打断未执行内容而强制重启商店。 + +同理,公共事件(包括公共事件商店)和自动事件本质上是“一次性”的条件为`false`的后置条件循环,因此使用这两个指令都能跳出它们。 + +另外,全局商店在录像中的记录方式是`"shop:id"`紧接着显示选择项的记录,也就是说“离开”项的序号可能会不固定(尤其是在连载塔中),请稍加留心。 + +## 数据相关类(绿色) + +![image](img/control_itemsg.jpg) + +这类的指令会设置各种数据(如怪物属性、楼层属性、全塔属性、七大可读写块),处理弹窗输入和开关全局商店,以及控制玩家最最关心的勇士的各种行为。 + +### 设置各种数据的指令 + +1. **数值操作:**最简单的就是最灵活的,本指令能够修改七大可读写块(状态、物品、变量、独立开关、临时变量、全局存储、增益)的值。 + * 修改的运算符有十种,“设为”会将右块的值代入左块,“增加、减少、乘以、除以”则是对左块的值进行增减和扩大缩小。 + * 除法如想只保留整数商(向零靠近)则改用“除以并取商”,如想要余数(例如想取勇士生命值的后两位)则使用“除以并取余”。 + * “乘方”指的是将若干个(不一定是正整数)左块连乘起来的积代入左块。 + * “设为不大于”和“设为不小于”是指在左块大于/小于右块的时候将右块代入左块,也就是“封顶”和“保底”的作用。 + * 指令的右块为一表达式,可以使用任何值块和运算块,甚至直接使用API. + * 如果需要连续使用本指令,建议除最后一个外都勾选“不刷新状态栏”,以降低刷新状态栏的性能损耗,并且避免意外触发自动事件、生命和魔力溢出甚至死亡(生命小于等于0)。 +2. **设置怪物属性:**可以设置怪物的任何一项属性并计入存档。 + * 怪物ID在blockly块中也可以填中文(要求没有重复,有的话请在事件编辑器顶部关闭“中文替换”功能),需要设置的属性项在下拉框中选取。通过配置表格自行新增的属性在下拉框里没有,但可以写在json区再单击变黄的“解析”按钮,或修改`_server\MotaAction.g4`文件最后的部分去追加。 + * 本指令对应`core.setEnemy(id, name, value, operator, prefix)`函数,完全等价。注意value会被eval,因此字符串需要额外套一层引号! + * 最后的“值”和“数值操作”的右块写法一致,注意设置怪物名称需要加引号,设置逻辑值(是否)需要填写`true`或`false`,设置“特殊属性”需要填数组且只支持结果值。 + * V2.8起,怪物支持“行走图朝向”功能,您在四个朝向的怪物中任意设置一个的属性都会强制同步到其他三个。 +3. **定点设置/移动或重置怪物属性:**V2.8新增,该指令主要是为了实现一些诸如“发动技能后指定某个点,该点怪物被削弱/增强,并计入存档”的功能。 + * 该指令支持设置的属性有“名称、生命、攻击、防御、金币、经验、加点”,设置后,该属性会在“脚本编辑——怪物真实属性”中最先被使用,然后可以被光环等影响。 + * 使用时,需要指定楼层和坐标,楼层不写则取当前层,坐标不填则取当前点,支持双击从地图选点,(除移动外)支持选多个点。 + * “移动某点怪物属性”支持写增量(dx、dy),如写[4,-2]就会让某个点的怪物属性移动到向右4格、向上2格的位置。 + * 发生战斗后,该点会被自动重置定点属性。怪物移动跳跃时(如阻击)定点属性会自动一起被移动,您无需手动移动。 + * 该组指令实际调用的API为: + ``` + core.setEnemyOnPoint(x, y, floorId, ...) + core.moveEnemyOnPoint(fromX, fromY, toX, toY, floorId) + core.resetEnemyOnPoint(x, y, floorId) + core.enemys.getEnemyValue(enemy, name, x, y, floorId) // 读取 + ``` +4. **设置装备属性:**V2.8新增,该项可以制作一些“随剧情推进而强化装备”的效果并计入存档。 + * 使用时,需要指定装备ID、要修改的是常数值还是增益、要修改的属性英文名等。 +5. **设置楼层属性:**除了贴图和两个到达事件,其他属性都可以方便地修改。 + * 楼层ID不填则视为当前楼层,可以去“地图选点”浏览各楼层并复制ID. + * 注意修改“楼层中文名”、“状态栏中名称”、“默认地面ID”、“背景音乐”(需要后缀名)这些字符串类型都需要加引号(V2.8起背景音乐支持一维数组),几个“能否/是否”只支持修改为`true`或`false`,三个坐标和天气、色调这些数组类型都需要加方括弧。本指令对应`core.events.setFloorInfo(name, value, floorId, prefix)` + * 修改当前层的天气/色调/背景音乐后不会立即生效,如需生效请补一个对应的特效指令(如“恢复画面色调”)并且不要勾选“持续”。 +6. **设置全局属性:**即全塔属性中的“主样式”(无需再加引号)和装备孔列表。 + * 修改装备孔列表时请注意,如果装备的道具属性中填写的装备类型是自然数,则可以【在列表结尾】随着游戏流程的推进解锁新的装备孔或移除装备孔(请先将身上的此类装备脱下)。 + * 而如果装备的道具属性中填写的装备类型是装备孔名称,则可以随着游戏流程的推进修改装备孔的类型组成,如本来是两把剑一块盾改成一把剑两块盾(同样需要注意已经穿上的装备问题)。 + * 本指令对应`core.events.setGlobalAttribute(name, value)`函数。 +7. **设置全局数值:**如题,可以修改四种宝石和三种血瓶的基础值等,必要时也可以修改图块的每帧时间以及上下楼时间以得到一些演出效果。 + * 如需使用脚本,请直接修改`core.values`,完全等价。但是竖屏状态栏的自绘行数如果动态修改有可能会出问题,请注意。 +8. **设置系统开关:**如题,可以用来随着游戏流程的推进解锁/移除状态栏的显示项或改动其他开关。 + * 比如中途开关生命上限、加点和负伤,中途显隐魔力、技能、绿钥匙和破炸飞毒衰咒。 + * 在游戏胜利时会将生命值作为分数上传到在线游戏排行榜,因此请在胜利前关闭生命上限再修改生命,比如根据钥匙等道具的持有情况进行加分。 + * 请注意,即使您在游戏中途才将楼传变为平面塔模式,访问过的楼层依然已经记录了最后离开的位置。 + * 本指令对应`core.setGlobalFlag(name, value)`函数,实际会修改`core.flags`(但请不要用脚本直接修改它) +9. **设置文件别名:**V2.8新增,您可以修改一个中文名实际指向的英文文件名,从而做到随剧情推进采用不同的系统音效(如上下楼)等效果,如果英文文件名不填则表示恢复到全塔属性中的默认值。 + +### 导致勇士位置变化的指令 + +这类指令都支持填写负坐标、超出地图宽高的坐标或小数坐标(大地图中请慎用小数坐标), + +当勇士在这些异常坐标时【除第一个指令外】都可以正常执行。 + +可以用于一些特殊演出,但请记得在事件结束(玩家恢复行动)前改回正常。 + +1. **勇士前进一格或撞击:**如题,会让勇士像自由行动时一样尝试前进一格。 + * 如果可以前进但前方不可被踏入(如门、怪物、箱子、NPC)则会撞击并触发事件,走到道具、踩灯或路障或用普通事件制作的陷阱等也会触发。 + * 本指令可以正常触发跑毒和阻激夹域捕捉(可以致死),滑冰事件就是在冰上执行了它。 + * 本指令对应`core.moveAction(callback)`函数,但请勿直接调用它。 +2. **无视地形移动勇士:**“动画时间”为每步的时间,不填则取玩家设定值,该值从V2.8起允许在移动过程中修改(最少为16)。 + * 可以勾选“不等待执行完毕”来和后面的指令同时执行,比如让勇士和一个NPC肩并肩走。 + * 本指令不会触发跑毒和阻激夹域捕捉,且会无视地形可进出性、可通行性。 + * 移动过程中不会触发任何事件,就像开启调试模式时按住Ctrl键移动一样(与之不同的是,也可以移动出地图外) + * 勇士后退时,跟随者们会照常前进,数不清楚格子时记得善用地图选点功能浏览地图。 + * V2.8起,支持斜向移动,支持移动过程中单纯转向(步数填0)。 + * 斜向移动时行走图以左右为准,但“后退”依然以勇士朝向为准而不考虑上一步的行走方向(这点和图块的移动不同,勇士不可能斜向后退但图块可能)。 + * 本指令对应`core.eventMoveHero(steps, time, callback)`函数,请注意不是`core.moveHero()` + * 多说一句,您可能会发现勇士在移动时会在行走图的2、4两帧之间反复切换(尤其是在大地图中心时很明显),这和图块以及RPG Maker的行为很不一致而且观感较差,如需改成1234帧循环,请启用“勇士四帧行走动画”插件(V2.8该插件有更新,从V2.7.x接档的用户需要重新抄写一下)。 +3. **跳跃勇士:**可以填写目标点坐标(支持双击从地图选点),坐标允许使用带有冒号缩写量甚至API的表达式。 + * 比如`["core.nextX(2)", "core.nextY(2)"]`(json)表示勇士越过面前一格,即道具“跳跃靴”的效果。 + * V2.7.3起,跳跃的目标坐标支持写增量(dx、dy),如写[4,-2]就会让勇士跳到向右4格向上2格的位置。 + * 跳跃高度和距离有关,原地跳跃的高度只有半格(可在下述函数中修改)。跳跃过程中跟随者消失,跳跃结束时跟随者聚集。 + * 跳跃也支持异步效果(如和NPC一起跳),对应`core.jumpHero(ex, ey, time, callback)`函数,其中`callback`为异步跳跃完毕的回调函数。 + * 跳跃默认没有音效,您可以自行像支援怪和道具“跳跃靴”一样配上音效(具体方法在“插件复写”一节有讲)。 + * 和“无视地形移动勇士”一样,勇士跳跃也会无视目标点的地形和阻激夹域捕捉,不会触发目标点的任何事件。 + * “无视地形移动勇士”和“跳跃勇士”因为经常和图块的这两个行为一起使用进行演出且都没有碰撞效果,因此V2.7.3起移动到了“地图处理类”,请注意。 +4. **楼层切换:**和前面的“楼梯、传送门”绿点事件用法完全一样,但不可穿透。 + * 此指令同样支持双击从地图选点(坐标支持表达式)和在json区填写传送的目标点图块ID(在目标层唯一)再点击变黄的“解析”按钮。 + * 另外,正如本小节开头提到的,本指令比起“楼梯、传送门”事件更多地用于演出,因此您可以填写异常坐标。 + * 楼层ID只能填写常量,如需使用变量,请使用“原生脚本”插入事件。 +5. **位置朝向切换:**“跳跃勇士”不会改变勇士朝向,“楼层切换”又会导致重生怪复活。且这两个都会导致跟随者聚集,所以同楼层内改变勇士位置可以使用本指令(坐标和跳跃一样支持双击从地图选点以及表达式)。 + * 本指令还可以用来让勇士原地转身(不填坐标,这样也不会聚集跟随者),支持4种绝对朝向和4种相对转向。 + +### “数据相关”类的其他杂牌指令 + +以下杂牌指令负责弹窗输入、显伤战斗、道具装备、全局商店、行走图和队伍: + +1. **接受用户输入:**弹窗请求用户输入一个自然数或字符串,提示文字允许使用`${表达式计算}`。 + * 请求输入自然数(支持十六进制)时,负整数会被取绝对值。小数会被向0取整,其他非法输入会变为0,“输入自然数”在录像中会记录为`input:n`。 + * 请求输入字符串时,玩家点击取消则视为输入了空字符串。 + * 输入的结果会保存在值块“`变量:input`(`flag:input`)”中,可供后续处理。 + * 比如样板的生命魔杖就是一个例子,它允许玩家一次使用多个同种道具。 + * 读取“全局存储”这一行为,在录像中和“输入字符串”的记录方式一致(`input2:base64`)。 + * V2.8起,录像回放中如果出现了多余的`input:`或`input2:`,都会警告并跳过。反之,“接受用户输入”事件在录像中缺失了值则会使用0或空字符串并补录。 +2. **更新状态栏和地图显伤:**如题,可以勾选“不检查自动事件”来不检查。 + * 本指令实际执行“脚本编辑——更新状态栏”,即`core.updateStatusBar(doNotCheckAutoEvents);` +3. **强制战斗(点名):**和天降怪物(指定ID,中文替换只支持不重复的中文名)强制战斗。 + * 此指令战后不会从地图删除图块也不会触发各点的战后事件(黄点),但可以触发战后脚本和怪物属性中的(批量)战后事件。 + * 此指令的战斗是强制的,打不过直接死亡(V2.8可以用值块预先判定能否打过)。 + * 此指令一般用于boss战(通过走到某个点或开启某个门来触发),可以制作战前剧情,然后强制战斗。 + * 战后boss不立即消失(从而避免基于漏怪检测的自动事件被误触发),可以继续进行一些演出,如51层魔塔四区骑士队长的逃跑效果。 + * 因为是天降怪物(没有坐标),所以对这只怪物在属性修正以及战损计算等处涉及到怪物坐标的代码一律不起作用。 + * 比如它不会受局部光环的加成,也不会被任何怪支援,也无法被V2.8的“定点设置怪物属性”影响。 + * 另一种强制战斗指令在“地图处理类”,指定的是坐标而不是怪物ID. + * V2.8新增了两种战前事件,两种强制战斗指令都不会触发它们,如需触发,请使用“触发系统事件”指令。 + * 由于V2.8提供了战前事件,因此不再建议使用曾经的“覆盖触发器+天降强制战斗”方式实现战前剧情,因为这样做不会自动存档,对玩家不太友好。 +4. **尝试使用道具和穿脱装备:**使用道具和穿戴装备需要指定ID(中文替换规则和强制战斗一样)。 + * 不能使用怪物手册(请使用“特效声音类”的“呼出怪物手册”指令)和楼层传送器(如果“覆盖楼传事件”则没有关系),使用中心对称飞行器则会跳过确认画面。实际对应`core.useItem(itemId)`函数。 + * 穿脱装备对应`core.loadEquip(equipId)`和`core.unloadEquip(type)`函数。脱下装备需要指定类型,这里只能写自然数不能写名称。 + * 道具使用失败或穿不上装备(比如没有或不满足条件)时会播放音效并提示。 +5. **开关全局商店:**本指令可以设置一个全局商店的启用和禁用状态,设为启用时也支持立即打开。 + * 一般用于商店的实体NPC处,再配合独立开关可以让勇士首次接触时先进行一些对话,然后启用(并打开)全局商店。 + * V2.8新增了值块可以用来判定一个全局商店是否是开启状态。 +6. **更改角色行走图:**如题,文件名必须填写(支持双击选文件)。 + * 文件必须放在`project/images`文件夹并注册,且规格必须符合要求(4帧总宽度至少128px,高度不限。宽高必须为4的倍数)。 + * 如果勾选“不重绘”就不会立即刷新,从而避免大地图视角重置到以勇士为中心。本指令对应`core.setHeroIcon(image, noDraw)`函数。 +7. **跟随者入队和离队:**您可以用这一对指令来让跟随者入队和离队,同样支持双击选文件。本指令对应`core.follow()`和`core.unfollow()`函数。 + * 行走图和勇士的规格要求(尺寸不需要一样)、文件位置和注册方法相同。 + * 离队可以不填文件名表示解散队伍只留勇士,如果填写文件名则尝试踢出队伍中第一个以此为行走图的跟随者。 + * 入队成功后、以及尝试离队后队伍都会聚拢,大地图视角也会重置。 + +## 地图处理类(浅蓝) + +![image](img/maps_waits_raw.jpg) + +这个类型的指令会影响三层地图矩阵的阵元,如果您觉得三层还不够用,“插件编写”(句号键)五图层插件欢迎您。 + +开始介绍前,首先明确一点:改变地图数据不会立即影响事件的进程(自动事件除外)。 + +比如因为踩灯、路障和阻激夹域捕捉怪的分布变化导致勇士行走被妨害的区域发生变化,但不会立即触发妨害效果,而是要等到勇士下次行走。 + +在勇士所在点转变成(显示)一个门/怪物/道具/箱子/楼梯什么的(包括在脚下转变成/显示冰面)都不会立即触发事件,把这些图块移动/跳跃到勇士身上也是。 + +反之,“隐藏事件”(转变图块为0)也不会立即中止当前的事件处理,只是下次不会触发。 + +1. **强制战斗(定点):**这是另一种强制战斗,它指定坐标而不是怪物ID. + * 可以双击从地图选点(只能选当前楼层的,不填则取当前点),也可以用表达式指定坐标,坐标一次只能写【一个点】。 + * 战斗后会自动从地图删除该点的怪物(重生怪则是隐藏),并尝试插入该点的战后事件(黄点)以及怪物属性的(批量)战后事件,成功插入前者时会改变当前点坐标到该点。 + * V2.8新增了两种战前事件,它们无法被“强制战斗”指令触发,如需触发,请使用“触发系统事件”指令。 +2. **开关门:**坐标写法同上(限1个点),同层开门时楼层ID可以略去不写。 + * 关门的位置必须是空地,“需要钥匙”只对同层开门有效。跨层开门请自己加判定,本指令对应`core.openDoor(x, y, needKey, callback)`函数。 + * 这对指令支持所有完整填写了“门信息”的四帧图块(自动元件除外),比如样板自带的三色墙和六色门。 + * 可以勾选“不等待执行完毕”来实现异步效果(如同时开关多个门,具体速度取决于门信息)。 + * 和上面的强制战斗一样,开门后将尝试插入该点的开门后事件(紫点),成功插入时会改变当前点坐标到该点。 +3. **显隐事件和图层块:**这两对指令可以令三层地图矩阵的某些阵元在0与非0之间切换。 + * 还以51层魔塔为例,二楼右下角的小偷在游戏开始时是不显示的,勇士进入四区后才出现。 + * 也就是说这个小偷是一个“普通事件”(红),内容是一些对话和打开35层魔龙处的暗道,只不过没有勾选“启用”。 + * 在适当的时候(这个例子中是和29楼小偷对话后),执行一个“显示MT2层(12,12)点处的事件”指令,就能显示出二楼的小偷。 + * 同理,勇士接触此小偷并处理事件,事件结束前执行一个“隐藏(同时删除)当前点事件,500毫秒”指令,小偷就会从画面中淡出,勇士可以任意在小偷存在过的位置走来走去而不会再触发什么。 + * 所以,一次性陷阱(走到某个地方关上墙/机关门、冒出埋伏怪)在触发后一定要及时隐藏。不然就会反复触发,样板1层有例子可供参考。 + * “显隐事件”都可以双击从地图选点,支持选多个点(只想要楼层ID的话可以点击“复制楼层ID”按钮)。在指令块中可以使用表达式作为坐标(但这样只能写一个点),多个点可以把横坐标依次填在x处而纵坐标对应填在y处(json中写作多行两列的二维数组,但只支持常数),从而同时显隐多个点。 + * 楼层ID省略则取当前楼层,“动画时间”用于同层显隐,从而表现出淡入淡出的效果。 + * “不等待执行完毕”的含义如前,您可以淡入一些点同时淡出另一些点。 + * 值得注意的是,“隐藏事件”还提供了一个“同时删除”勾选框,勾选后无法再用“显示事件”指令显示出来(例如永久移除一个重生怪)。 + * 请注意,隐藏或删除后不影响正在进行的事件流(不会立刻结束),您可以把该点安全地直接转变为别的图块或让别的图块移动/跳跃到此点,比如把箱子/阻击怪推过来(根据该点是否覆盖触发器,推过来以后的行为可能有变化)。 + * 其他两个图层的图块也支持显隐,对游戏性的影响主要体现在显隐背景层的滑冰图块以及两个图层的单向通行箭头/薄墙。坐标和楼层ID的填法同上,只不过这两个就没有淡入淡出效果了。因为其他两个图层的图块不支持什么初始隐藏,如有需要,可以在“开场剧情”中统一提前隐藏。 + * 显隐事件对应`core.showBlock(x, y, floorId)`和`core.hideBlock(x, y, floorId)`,同时删除对应`core.removeBlock(x, y, floorId)`函数;显隐图层块对应`core.maps._triggerFloorImage(type, loc, floorId, callback)` +4. **转变图块和图层块、事件转向:**这组指令可以修改三层地图矩阵的阵元。 + * 先说图层块吧(前景、背景),坐标和楼层的填法同上,不支持淡入淡出。转变图层块后,块的显隐状态不变,原来是显示/隐藏还是显示/隐藏。 + * 接着说事件层,坐标和楼层的填法同上。有几个注意事项: + 1. 新图块为0时“动画时间”全部用来淡出,用于没有普通事件和“楼梯、传送门”的点会视为删除。 + 2. 转变图块也不影响显隐状态,该点原来是显示/隐藏还是显示/隐藏。 + 3. 同层把一种非0图块转变为另一种非0图块(空气墙`airwall`算非0),“动画时间”的前一半用来淡出原图块,后一半用来淡入新图块。 + 4. 同层把0图块转变为非0图块,“动画时间”全部用来淡入。 + * 这对指令可以填写新图块的ID也可以填写数字(如17是空气墙,201起是怪物)。 + * 如需让绑定了“行走图朝向”的图块转向,也可以直接使用“事件转向”指令(和勇士一样支持7种转法),从而避免一个个手写行走图ID的麻烦。 + * 转变图块和图层块对应`core.setBlock(number, x, y, floorId)`和`core.maps._triggerBgFgMap(type, name, loc, floorId, callback)` +5. **设置图块不透明度和特效:**如题,V2.8新增。前者支持渐变效果,可以用来制作亡灵状态的角色或配合楼层贴图实现大型多帧怪物。 +6. **显隐贴图:**这个指令可以用来显隐之前在“楼层属性”中介绍的楼层贴图。 + * 显隐贴图不支持淡入淡出,坐标为贴图左上角的【像素坐标】因此不支持地图选点,楼层ID不填则取当前层。实际执行`core.maps._triggerFloorImage(type, loc, floorId, callback)` +7. **移动和跳跃事件:**这两个指令可以将地图一点的图块转移到另一点。 + * 首先明确一点,这两个指令转移的【仅仅是图块】。起点的七彩事件不会被一同转移(但不消失的情况下定点属性会一同转移),终点的七彩事件也不会被覆盖。 + * 从游戏性上讲,最终的效果是起点被“隐藏事件+同时删除”,勾选“不消失”时终点被“转变图块+显示事件”(终点原来的图块被覆盖)。 + * 比如,阻击怪是“移动后不消失”,捕捉怪是“移动后消失”,支援怪是“跳跃后消失”。 + * 这两个指令一次只能转移一个图块,双击从地图选点选择的是移动的起点和跳跃的终点(跳跃的起点请右击选取)。 + * 任何一个坐标不填都视为当前点,比如“跳跃事件”什么坐标都不填就会让当前点图块原地跳跃。 + * 和无视地形移动勇士一样,“移动事件”也没有碰撞效果,移动过程中会穿过勇士和一切地形。 + * “动画时间”为每步移动的时间或跳跃的用时,以及不勾选“不消失”时淡出的时间。 + * 和“跳跃勇士”一样,“跳跃事件”默认也没有音效,可以自己搭配。 + * V2.7.3起,跳跃的目标坐标支持写增量(dx、dy),如写[4,-2]就会让某个图块跳到其原位置向右4格向上2格的位置。 + * 移动和跳跃实际对应`core.moveBlock(x, y, steps, time, keep, callback)`和`core.jumpBlock(sx, sy, ex, ey, time, keep, callback)`函数。 + * V2.8起,这两个函数在“不消失”的情况下,会将起点处的定点怪物数据也一并移动到终点,这对阻击怪来说是个福音。 + * V2.8起,“移动事件”指令支持斜向移动(行走图仍取左右)、中途变速(速度不得小于16)、中途转向(步数填0),“后退”指令如果用在开头则必须是绑定了“行走图朝向”的图块,如果用在中途则会根据上一步移动/转向的方向后退(注意这一点和勇士不同,勇士是不可能斜向后退的)。 + * “不等待执行完毕”的用法如前,但几个图块再加上勇士以各异的速度和总步数移动时安排起来很麻烦,需要用到下述的“等待三姐妹”。 + +## 等待三姐妹、注释和原生js/json + +在讲解“事件控制”(流程控制)类指令之前,这里插播几个比较杂牌的指令。 + +1. **等待固定时间:**如题,可以用来实现复杂的集体移动、跳跃效果。 + * 比如说51层魔塔一区结尾的骷髅埋伏圈,就是九只骷髅和四扇机关门组成的复杂演出。 + * 每只骷髅开始移动时都“不等待执行完毕”,但又需要“等待一小段时间”再让下一只骷髅开始移动。 + * 本指令还提供了一个勾选项“不可被Ctrl跳过”,如果不勾选此项且当前【没有】正在执行的异步事件(动画、音效、气泡提示不算),则Ctrl可以跳过等待。 +2. **等待所有异步事件执行完毕:**让我们来想象这样一个情景。 + * 您使用了“移动事件”来移动一只怪物到勇士面前,并且“不等待执行完毕”。而下一条指令是“勇士前进一格或撞击”,以期触发战斗。然而因为怪物移动需要时间,两个指令同时执行,所以战斗没法触发。 + * 类似地,如果您在一个异步事件执行完毕之前就结束了整个事件流,让勇士恢复行动,那么可能这些异步事件还没来得及在游戏性方面生效,导致接下来会发生的事取决于玩家操作的时机和勇士的移速。 + * 考虑到录像系统,在录像回放时很多耗时的东西和所有需要用户响应的东西会被跳过,勇士的移速又可以很快(倍速播放),导致回放结果和原游戏不一致。 + * 总之,当您希望确保一些异步事件完全生效后再开始执行新的指令或结束事件流,“等待所有异步事件执行完毕”就是您的不二之选了,事件编辑器也会在发现缺少本指令时弹窗警告。 + * 动画默认会被等待,而音效不会被默认等待;V2.8.1提供了两个勾选框允许您对其进行控制。 + * 另外,您可以随时使用`core.getPlayAnimates()`和`core.getSounds()`获取当前未结束的所有动画和音效的id(支持自变量填名称来筛选)。 +3. **等待用户操作并获得键鼠/触屏信息:**前面提到三种QTE指令,这是最后一种。 + * 之前提到的“确认框”和“选择项”可以复现RPG Maker的回合制战斗,但没法做出更复杂的交互界面,比如技能/天赋树,以及样板的道具商店,这就需要用到本指令了。 + * 本指令会阻塞事件的执行,直到玩家按下键盘上的某个键(滚轮视为PageUp/PageDown键)、或点击【视野】中的某个点、或经过了超时毫秒数(不设置则不限时)。 + * 解除阻塞后,下列值块可能发生变化: + 1. `变量:type`(`flag:type`),解除的原因,0表示按键,1表示点击,-1表示超时。 + 2. `变量:keycode`(`flag:keycode`),按键情况下的键值,48—57为大键盘0—9,65—90为字母键A—Z,其他键请右击该子块查询(查不到的自己百度)。 + 3. `变量:timeout`(`flag:timeout`),进行有效操作后,距离超时的剩余倒计时,可以用来进行一些处理(音游?)。 + 4. `变量:x`和`变量:y`(`flag:x`和`flag:y`),点击情况下所点格子在视野中的相对坐标,一定在0—12或0—14范围。 + 5. `变量:px`和`变量:py`(`flag:px`和`flag:py`),点击情况下所点像素在视野中的相对坐标,一定在0—415或0—479范围。 + * 上述后两项如需转换为大地图中的绝对坐标,请查阅“楼层属性——修改楼层宽高”一节。 + * 您可以根据这些值块去做流程控制,较为简单的场合(如几个键执行同一段指令,或横平竖直的单个矩形点击区)也可直接使用图中的场合块。 + * 其中点击的场合块还支持双击预览判定区,用半透明的红色标出。 + * 默认情况下,一次操作同时满足多个场合块时会依次执行这几个块的内容,除非您使用了下面的“不进行剩余判定”。 + * V2.7.3起,场合块支持“不进行剩余判定”,当满足勾选了此项的场合块时,它下面的其他场合块会全部视为不满足。这允许您实现“点击地图上某个小区域做某些事,点击其他地方做另一些事”而不需要在后者的场合中专门排除前者。 + * V2.8起,提供了超时场合块和自定义条件的场合块,同时还支持“只检测子块”。 + * 在“只检测子块”模式下,如果玩家进行了不符合任何子块的操作,那么会继续阻塞而不是进入下一个指令,超时倒计时也不会重置。 + * 但是如果设置了超时时间,即使未提供超时场合,超时时依然会解除阻塞直接进入下面的指令。 + * 此指令获取到的玩家操作,在录像中会像“接受用户输入数字”一样记录为`input:...`。例如不带超时的键盘按键,就会记录为`input:keycode`。 + * 录像回放时,如果出现了多余的`input:...`,就会跳过并警告。如果遇到本指令但录像中未记录操作或记录了非法/无效操作,就会直接报错。 + * 非法操作指“本指令没有设置超时时间但录像中记录为超时”,无效操作指“本指令设置为只检测子块,但录像中记录的是一个不满足任何子块的操作”。 +4. **添加注释:**在上述的场合块里您还可以看到两个注释块。 + * 注释块在游戏中会被忽略,一般用来帮您记住写的指令是用来做什么的。 + * 极端情况下,样板某些场合会检测指令数组是否为空及其长度,此时您可能需要被迫用注释指令来占位。 +5. **自定义事件:**自带的指令毕竟有限,但事件可以与脚本任意联动。 + * 原生脚本分为两种,原生js和原生json.其中后者会实际执行您通过`core.registerEvent`注册的事件处理函数(即`{"type":"xxx",...}`对应`core.events._action_xxx()`),请注意这类函数在执行结束前务必调用`core.doAction()`函数去继续执行下一条指令。 + * 如果学有余力,还可根据[修改编辑器](editor)来代替原生json,就可以使用调色器、地图选点、选文件等功能啦。 +6. **原生JS脚本执行**:允许您执行任意JS脚本,例如造塔测试时发现事件流程不符合预期,此时可以使用`console.log()`语句在控制台输出日志信息进行检查。 + +原生js的用法就更广了,首先它可以做一些事件做不到的事,比如: + +如果用事件增加道具的话就会有提示和音效,而这有时不是我们需要的,尤其是在设计新道具时将“能否使用”填`"true"`并在使用效果事件中使用失败的场合返还道具时;因此我们可以直接调用脚本: +```js +core.addItem(itemId, n); // 静默增加n个道具,n可为负数,不填视为1 +``` + +其次,受制于Antlr—blockly的数据类型,很多指令的参数只能写常数,比如楼层ID。这时我们就需要在原生js中使用万能的`core.insertAction()`大法,来突破这些限制。 + +比如说我们有一座20层高的塔,楼层ID为MT0—MT19,某个事件中我们想让勇士随机传送到某个楼层,坐标不变。那么就可以使用下列原生js: +```js +core.insertAction({"type": "changeFloor", "floorId": "MT" + core.rand2(20)}) +``` + +连续使用多条json指令时,请先声明一个空的指令数组(`var todo = [];`)然后将需要的指令分批追加到其末尾(`core.push(todo, [...]);`),最后再一次性`core.insertAction(todo);`插入执行,可以选择插入到剩余事件的开头或结尾。 + +另外您可能会问:既然都用js了,为什么不直接用之前提到的`core.changeFloor()`函数呢? + +这是因为,原生js在不勾选“不自动执行下一个事件”的情况下,**只能使用瞬间完成的函数(或者drawTip、动画和音效这种虽然耗时但不影响游戏性的),不能使用任何异步函数(包括阻塞直到玩家操作的)**! + +系统常见可能会被造塔用到的API都在[API列表](api)中给出,一般而言异步API的最后一个自变量都叫`callback`(回调)。在勾选“不自动执行下一个事件”的情况下,原生js可以使用一个异步API,只需将其`callback`参数填`core.doAction`,请谨慎使用。 + +比如说,我们知道天降强制战斗没有坐标所以不受光环等影响也无法触发(单点)战后事件,那捕捉怪的战斗是怎么实现的呢?答案是在原生js中使用了异步的`core.battle(id, x, y, force, callback)`函数,这里`force`填`true`表示强制战斗,`callback`填`core.doAction`表示战斗结束后继续处理事件。 + +熟练运用后,还可以使用多个异步API,每个以下一个作为回调。 + +## 事件控制类(深蓝) + +![image](img/flowctrl.jpg) + +在三个QTE指令中,我们已经初见了流程控制的端倪。只不过,它们的流程走向是由玩家的选择直接左右的。能否通过对值块的比较等操作自动走向不同的流程分支呢?答案是肯定的。 + +1. **条件分歧:**和“显示确认框”很相似,只不过这个是自动的。 + * 和js的`if (...) {...;} else {...;}`完全等价,本指令需要内嵌一个值块(可以填逻辑表达式,常常是两个值块的比较,比如某道具是否足够)。 + * 当此值块的值不为`false、0、null、undefined、NaN和空字符串`(即之前提到的六大广义`false`)时,执行“如果:”和“否则:”之间的指令,当此值块的值为这六个值之一时,执行“否则:”后面的指令。 +2. **多重分歧:**本指令和js的`switch`语句有一定差别,即使您有js或其他编程语言基础,也请务必仔细阅读接下来的说明。 + * 事件执行到多重分歧指令时,会先计算一次“判别值”,结果记作`key`.然后准备一个空的指令数组,记作`list`; + * 接下来从上到下扫描每个场合块,如果一个场合块的“值”为`default`或计算结果和`key`相等,就把这个场合的指令组追加到`list`的末尾,即`core.push(list, [...]);`。 + * 每次成功追加后,如果被追加的此场合块未勾选“不跳出”,则无视下面的所有场合块直接结束扫描,否则继续扫描下一个场合块。 + * 扫描完全结束后,调用`core.insertAction(list);`付诸执行。 + * 所以各场合块的顺序一定要安排好,比如`default`(可以没有此场合)如果不勾选“不跳出”则一定要放在最后。 + * 多重分歧常见的用法是,判别值填未知量,所有场合块都不勾选“不跳出”且“值”填写为两两不同的常量(如果有相同的则只有第一个有效)。从而执行唯一的相等场合,没有的话还可以补一个`default`场合,类似js的`else if`语法。 + * 或者,判别值填常量,各场合块填未知量且都勾选“不跳出”。把所有相等的场合筛选出来依次执行,类似js的`filter`语法。 + * 第二种用法中,最常见的是判别值填`true`,各场合块填逻辑表达式。此时如果都勾选“不跳出”并且不提供`default`场合,就相当于一堆没有`else`的if语句。 + * 而如果在这个基础上改成都不勾选“不跳出”就相当于正常的`else if`语句(可以把`default`场合提供在最后或不提供),而且比起上面的“条件分歧”指令不需要嵌套了。 +3. **循环遍历(计数):**使用临时变量A—Z(事件流结束时会和arg+纯数字的变量一起被清空)可以进行循环遍历,它们都属于前置条件循环。 + * 循环遍历有两种,第一种是计数型。`从`和`到`之间的框填写临时变量的初始值,`到`和`步增`之间的框填写临时变量允许的边界值。 + * 每轮循环开始前会【重新计算】边界值和步增,如果临时变量位于界外(减去边界值后和步增同号)则跳出循环。 + * 每轮循环结束时,临时变量会加上步增值(可以为负数,请注意变号问题)。 +4. **循环遍历(迭代):**这是另一种循环遍历,可以迭代一个列表。 + * 每轮循环前,会检查列表是否已空。已空则跳出循环,否则将列表的第一项【移除并代入】临时变量。 + * 使用此项时请注意,列表中的字符串不会被自动解释为冒号缩写量。显示文章等处如有需求,请使用`${core.calValue(temp:A)}`来计算。 + * 分歧和循环都可以嵌套,嵌套循环遍历请给内外层使用不同的临时变量。 +5. **前/后置条件循环:** + * 前置条件循环和一个没有“否则”分支的条件分歧很相似,区别仅仅在于每次执行完分支内容后都会【跳转】回到条件检测之前,从而再次检测条件,还满足就再执行,如此往复。 + * 而后置条件循环比起前置,区别在于第一轮循环是无视条件强制执行的。 + * 它们对应的js语法分别为`while (...) {...;}`和`do {...;} while(...);` +6. **跳出当前循环:**如题,遇到此指令时,将检测当前是否在某个循环中(还包括自动事件、公共事件、全局商店),并跳出所在的【最内层】循环(V2.7.3起支持指定跳出层数)。如果不在任何循环中,则什么也不做。大致相当于js的`break;` +7. **提前结束本轮循环:**生效范围同上,不过是结束最内层的【本轮】循环。换言之: + * 对一般的前/后置条件循环会立刻跳转到下次检测条件前; + * 对循环遍历(计数)会立刻跳转到下次计算边界和步增并累加前; + * 对循环遍历(迭代)会立刻跳转到下次检查列表是否为空前。 + * 还可用来“重启新版商店或道具商店”。若不在任何循环中,则什么也不做。 + * 大致相当于js的`continue;`,V2.7.3起支持指定层数。 +8. **立刻结束当前事件:**此指令将清空临时变量(以`@temp@`开头)和参数变量(`arg+纯数字`),清空事件列表,中断一切事件处理,恢复勇士行动。 + * V2.8起,您可以在两种“战前事件”中使用它取消战斗。 +9. **触发系统事件:**模拟勇士撞击/踩踏本楼层的某个点(实际执行`core.trigger(x, y, callback)`),支持双击从地图选点。 + * 该指令把触发的事件(包括图块属性的“碰触脚本/碰触事件”,不包括阻激夹域捕捉和血网)插队到当前事件列表中。 + * 譬如该点是道具则会捡起来,是怪物则会触发战前事件然后战斗,是门则会尝试开门,还会连带触发对应的`afterXxx`事件。 + * 如果该点是普通事件(红)则会触发之,是楼梯事件(绿)则会直接触发传送(目标点是“保持不变”或三种对称点也以勇士位置而不是楼梯位置计算)。 + * 该点是路障则会触发对应的毒衰咒(血网除外),是踩灯则会把勇士脚下变成踩过的灯。滑冰在背景层,所以无法触发。至于推箱子,请自行探索。 + * V2.8起,您可以使用“强制战斗(定点)”指令跳过战前事件强制与地图上某个点的怪物战斗,也可以用“触发系统事件”指令指定某个有怪物的点然后先触发战前事件再战斗,请注意区分。 +10. **插入公共事件:**如题,“参数列表”为一数组,其各项会被依次代入值块`变量:arg1`、`变量:arg2`...而`变量:arg0`会记录公共事件的名称。 + * 实际执行`core.insertCommonEvent(name, args, x, y, callback, addToLast)` +11. **插入事件:**此指令可以无视目标点启用与否,跨楼层插入任何点的普通事件(不需要覆盖触发器)、战前事件、afterXxx事件执行。支持双击从地图选点。 + +## 特效声音类(褐色) + +![image](img/blockly.jpg) + +这个类别的指令会负责动画、视角、色调、天气、音频、存读档等其他一些细节。 + +1. **画面震动:**会让画面震动,可以选择四种振动方向,可以指定总时间、震动速度和振幅。 + * 实际执行`core.vibrate(direction, time, speed, power, callback)`函数。 +2. **显示动画:**如题,可以双击选文件并预览(预览的坐标锁定为视野中心)和试听/修改音效。 + * 如需从地图选点请右击指令块,也可用另一个指令让动画跟随勇士移动。 + * 坐标不写则取当前点,如果勾选“相对窗口坐标”则坐标应填写为0—12或0—14表示视野中的相对坐标(如13×13样板填两个6表示视野中心)。 + * 如果不勾选“不等待执行完毕”,则等待的实际时长只取决于动画,和音效无关。 + * 实际调用`core.drawAnimate(name, x, y, alignWindow, callback)`和`core.drawHeroAnimate(name, callback)` +3. **设置视角:**设置视角支持双击从地图选点,不填坐标则重置视角。动画时间指移动的总时间,支持四种变速移动。 + * 勇士的`status:animate`为`true`(原地抖动开启)时,禁止使用本指令! + * V2.7.3起,坐标支持写增量,如[4,-2]表示视角直线移动到向右4格、向上2格的位置。 + * 请注意,勇士重绘时(`core.drawHero()`函数)视角也会随之重置。所以视角变化后勇士的坐标、朝向、显隐、行走图(事件和API都提供了不重绘参数)和跟随者情况暂时都不要动。 + * 实际调用`core.setViewport(x, y)`和`core.moveViewport(x, y, moveMode, time, callback)`,其中前者的自变量为【像素坐标】且不必为32的倍数,必要时可作为原生js使用来实现特殊的演出。 + * V2.8.1起,支持暂时锁定视角,视角锁定期间,只有上下楼后才会将视角重置到勇士身上(但依然保持锁定)。 +4. **显隐状态栏:**如题,如果隐藏状态栏期间勇士需要恢复行动,则建议不隐藏竖屏工具栏以方便手机玩家。 + * 实际调用`core.showStatusBar()`和`core.hideStatusBar(showToolbox)` +5. **设置勇士不透明度:**如题,动画时间为淡入淡出时间,异步勾选框用法如前。 + * 实际调用`core.setHeroOpacity(opacity, moveMode, time, callback)` +6. **更改画面色调:**色调可以用调色器调配,“动画时间”为渐变的总时间。 + * 请注意渐变是在RGBA颜色空间中直线运动(V2.8.1支持加速度),因此效果可能不好,画面闪烁同理。 + * 如需在勇士自由行动时反复执行,请使用并行脚本或自我回调。 +7. **恢复画面色调:**指将更改后的色调恢复到楼层的默认色调,更改当前层的默认色调后您可以使用此指令刷新。 +8. **画面闪烁:**“单次时间”必须为3的倍数,前1/3时间用于将画面色调转变为目标色调,后2/3时间用于恢复当前色调,执行次数如题。 + * 实际调用`screenFlash(color, time, times, callback)` +9. **更改天气:**如题,可以选择“无、雨、雪、雾、晴”之一,强度需填小于等于10的正整数。 +10. **播放背景音乐:**如题,可以双击选文件并试听(支持别名),并指定开始播放的秒数。 + * 当在游戏中触发楼层切换时(包括读档),如果`flag:__color__、flag:__weather__、flag:__bgm__`这三个值块没有值,游戏当时的画面色调、天气、背景音乐就会变为楼层属性中的这三个设置项。 + * 以上几个指令都提供了“持续到下个本事件”勾选框,勾选后,本次设置的值将会计入这三个值块。它们的拦截行为在“脚本编辑——切换楼层中”。 + * 若不勾选,或恢复画面色调、设置天气为无(晴),就会清除对应的值块。您也可以随时对这三个值块进行手动干预。 + * V2.8.1起,支持由作者注册新的自定义天气,新天气在下拉框中默认未提供,可以手动填写在json区再点击“解析”按钮。 +11. **暂停和恢复背景音乐:**如题,暂停时会记录暂停在了第几秒,恢复时可以选择从这个秒数继续或从头重播。 +12. **预加载背景音乐和释放其缓存:**在线游戏使用,前者的原理是静音播放。 + * 最多同时缓存8首背景音乐(由`libs/core.js`控制),会自动释放最久未使用的,但您也可以手动释放。 +13. **播放音效和停止所有音效:**如题,开播一个音效的同时可以停止其他的。V2.8播放系统音效可以从下拉框选取,其他音效也支持使用别名。 + * V2.8起,播放音效支持“等待播放完毕”,支持同时“变调且变速”。100表示正常速度和音调,可以填30到300之间的值。 +14. **设置音量:**只对背景音乐有效,音量为小于等于100的自然数(玩家设置值其实被开了平方),渐变时间如题。 +15. **设置背景音乐播放速度和音调:**V2.8新增,需要填30到300之间的值。100表示正常速度和音调,可以只改变速度(RPG Maker做不到哦),也可以同时改变速度和音调。 + * 该项的效果不会进存档,必要时您可以配合使用“禁止存档”指令。 +16. **呼出怪物手册和SL界面:** + * 呼出手册只在勇士持有手册时生效,关闭手册后事件继续。 + * 呼出存档界面最多只能存一个档然后事件继续(读档后事件现场会恢复)。 + * 呼出读档界面如果不读档则事件继续,录像回放中这三条指令都被忽略。 +17. **自动存档:**读档后也会恢复事件现场,录像回放中会照常存档。 + * 如不勾选“不提示”则会`core.drawTip(“已自动存档”)`(即使在下面禁止的情况下!),此指令一般用于选择项/确认框之前。 +18. **是否禁止存档:**该项允许暂时禁止玩家存档(包括撞怪撞门的自动存档,不包括上面的呼出存档界面和事件中自动存档的指令),从而做出RPG中常见的“迷宫中只有特定位置可以存档,或者必须消耗道具进行存档”的效果。 + +## UI绘制类(瞬间) + +![image](img/uievent.jpg) + +这个类别的指令全部是瞬间完成的,有一一对应的API,请放心使用,除“绘制多行文本”外都可以逐个双击预览(多行文本需要右击预览)。游戏中,这些指令都是画在`uievent`层的。 + +1. **UI绘制并预览:**您可以把一堆UI绘制类指令塞进去然后双击黄框整体预览。 +2. **清除画布:**擦除`uievent`层的一部分,x和y为左上角坐标。 + * 四个参数都支持使用表达式,任何一个参数不填都会删除整个`uievent`层。 + * 实际调用`core.clearMap("uievent",x,y,width,height)`和`core.deleteCanvas("uievent")`,熟练后对其他画布使用也是可以的。 +3. **设置画布属性:** + 1. 字体:`italic和bold`表示斜体和加粗,可省略,字号和字体用于绘制文本。 + 2. 填充样式:绘制实心图形时的默认填色,可用调色器调配。 + 3. 边框样式:绘制空心图形时的默认边框颜色,可用调色器调配。 + 4. 线宽度:绘制线段、箭头和空心图形时的默认线宽,单位为像素。 + 5. 不透明度:不大于1的非负数,此项为“画笔”的不透明度。只影响接下来画的内容,已经画上去的不受影响。 + 6. 对齐:绘制单行文本的对齐方式,左对齐、左右居中、右对齐。 + 7. 基准线:绘制单行文本的基准线,有六种写法。绘制单行文本的坐标其实是文本的基准点, + 8. z值:初始为135,z值较大的画布将覆盖较小的,详见“个性化”。闪烁光标的z值总是比上述的值大1,即默认为136. +4. **绘制文本:** + * 这里仅绘制单行文本,坐标为基准点的像素坐标,需配合上述“对齐”和“基准线”使用。 + * 如果设置了最大宽度,那么在超出此宽度时就会保持比例压缩到这个宽度。 + * 正文只支持`${js表达式求值}`和`\r[变色]`,不支持其他一切转义序列,更不能手动换行。 + * 实际执行`core.fillText("uievent", text, x, y, style, font, maxWidth)` +5. **绘制描边文本:**同上,但不支持限宽,描边效果同状态栏数字的黑边。 + * 底层实现为`ctx.strokeText()`,描边颜色可以自己指定。 + * 本指令对应的js函数为`core.ui.fillBoldText('uievent', text, x, y, style, font)` +6. **绘制多行文本:**双击进入多行编辑,预览请右击。 + * 起点像素为左上角,只有设置了最大行宽才会对齐、居中和自动换行。 + * 如果不设置颜色和字号,就会采用“设置剧情文本的属性”中的正文设置。 + * 不设置行距就会采用字体大小的1.3倍,建议采用偶数字号和1.5倍行距。 + * 多行文本不支持字体样式的设置,使用的是全塔属性中的全局字体`Verdana`, + * 如有需要,请使用“设置全局属性”指令来设置字体样式。 +7. **绘制几何图形:**对应的js函数为`core.ui.fillXxx()`和`core.ui.strokeXxx()` +8. **绘制图片:**同“显示图片”指令但功能有出入,实际执行`core.drawImage('uievent',img,x,y,w,h,x1,y1,w1,h1)` +9. **绘制图标:**支持图块id和系统图标,支持伸缩和选择哪一帧。支持32×32和32×48两种尺寸,实际执行`core.drawIcon(name, id, x, y, w, h, frame)` +10. **绘制背景图:**背景色支持颜色数组,也支持类似`winskin.png`的图片名。使用图片时的不透明度以及纯色背景时的边框颜色,由“设置画布属性”指定。本指令对应的js函数为`core.ui.drawBackground(left, top, right, bottom, posInfo)` +11. **绘制和清除闪烁光标:**如题,光标的z值总是比`uievent`层大1. +12. **设置画布特效:**V2.8新增,允许给画布设置像图块一样的“虚化、色相、灰度、反色、阴影”属性。 + +========================================================================================== + +[继续阅读下一章:个性化](personalization) diff --git a/_docs/personalization.md b/_docs/personalization.md new file mode 100644 index 0000000..344105f --- /dev/null +++ b/_docs/personalization.md @@ -0,0 +1,441 @@ +# 个性化 + +?> 在这一节中,让我们来了解更多对样板个性化的修改 + +有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。 + +## 图层的说明 + +HTML5魔塔是使用画布(canvas)来绘制,存在若干个图层,它们之间有一个覆盖关系,后面的图层将覆盖前面的图层。 + +所有图层从低往高依次如下:(加[B]的代表该层是大地图,[D]代表由系统按需动态创建,z-index代表该层的纵向高度) + +- bg**[B]**:背景层;绘制背景图层素材bgmap,和背景贴图 (z-index: 10) +- event**[B]**:事件层;所有事件(道具、墙壁、NPC、怪物等)都绘制在这一层进行处理 (z-index: 30) +- hero:勇士层;主要用来绘制勇士 (z-index: 40) +- event2**[B]**:事件2层;本层主要用来绘制48x32的图片素材的上半部分(避免和勇士错位) (z-index: 50) +- fg**[B]**:前景层;绘制前景图层素材fgmap,和前景贴图 (z-index: 60) +- damage**[B]**:显伤层;主要用来绘制怪物显伤和领域显伤 (z-index: 65) +- animate:动画层;主要用来绘制动画。 (z-index: 70) +- weather**[D]**:天气层;主要用来绘制天气(雨/雪/雾) (z-index: 80) +- route**[D]**:路线层;主要用来绘制勇士的行走路线图。 (z-index: 95) +- curtain:色调层;用来控制当前楼层的画面色调 (z-index: 125) +- image1\~50**[D]**:图片层;用来绘制图片等操作。(z-index: 100+code, 101~150) +- uievent**[D]**:自定义UI绘制层;用来进行自定义UI绘制等操作。(z-index:135,可以通过事件设置该值) +- ui:UI层;用来绘制一切UI窗口,如剧情文本、怪物手册、楼传器、系统菜单等等 (z-index: 140) +- data:数据层;用来绘制一些顶层的或更新比较快的数据,如左上角的提示,战斗界面中数据的变化等等。 (z-index: 170) + +请注意:显示图片事件将自动创建一个图片层,z-index是100+图片编号。 + +而,色调层的z-index是25,ui层的z-index是140;因此,图片编号在1~24的将被色调层遮挡,25~40的将被ui层遮挡,41~50的将遮挡UI层。 + +uievent层为自定义UI绘制所在的层,其z值初始是135,可以通过事件设置;自定义绘制的闪烁光标所在层的z值永远比该值大1。 + +### 动态创建canvas + +从V2.5.3开始,可以在H5样板中任意动态创建canvas并进行使用。 + +使用`core.createCanvas(name, x, y, w, h, z)`来动态创建一个画布。 + +其中name为动态canvas名称,x,y,w,h为创建的画布相对窗口左上角的像素坐标和长宽,z为画布的纵向高度。 + +例如:`core.createCanvas('test', 10, 20, 100, 200, 74)` 创建了一个名为test的画布,其左上角相对窗口的像素坐标为(10,20),宽100高200,纵向高度74(在动画层和天气层之间)。 + +该函数会返回画布的context,也可以通过 `core.dymCanvas[name]` 来获得;例如 `core.dymCanvas.test` 就是我们上面创建的画布的context,然后进行操作。 + +也可以简单的使用`core.fillText()`, `core.fillRect()`, `core.strokeRect()`等等对画布进行任意绘制。 + +``` js +core.fillText('test', '这是一段文字', 10, 30, '#FF0000', '16px Verdana'); // 绘制一段文本 +``` + +使用 `core.deleteCanvas(name)` 删除一个动态创建的画布,例如 `core.deleteCanvas('test')`。 + +`core.deleteAllCanvas()`可以删除所有动态创建的画布,`core.relocateCanvas(name, x, y)`和`core.resizeCanvas(name, x, y)`可以对画布的位置和大小进行改变。 + +更多详细API请参见[API列表](api)。 + +## 单点多事件 + +带有独立开关的商人算是一个最为简单的单点多事件的例子了(提供在了事件编辑器左侧的“常用事件模板”),单点多事件最常用的指令就是“转变图块”(或关门)和“条件分歧”。下面以几个具体例子来进行详细说明: +1. 打怪变成门或道具、开门变成怪或道具:怪物、门、道具都是系统触发器,直接利用afterXxx事件。如: + * 战后事件:关门`yellowDoor`(原地关上了一扇黄门) + * 战后事件:转变图块为`yellowKey`(怪物掉落了黄钥匙) + * 开门后事件:转变图块为`greenSlime`或`yellowKey`(开门变成了史莱姆或黄钥匙) + * 打怪变成别的怪、开门关上别的门、门和怪来回变的,用独立开关计数。 + * 如有批量需求,请使用“脚本编辑 - `afterXxx`函数或怪物的(批量)战前/战后事件。 +2. 打怪/开门/捡道具后变成传送点或npc: + * 这时的传送点就不能用“楼梯、传送门”事件了,而应该用只有一条“场景切换”指令的普通事件。 + * 此事件不勾选“覆盖触发器”,这样怪物、门和道具的系统触发器会触发。 + * 在对应的`afterXxx`事件中“转变图块为”传送点或npc,淡入效果更佳哦。 + * 请注意,因为道具是可通行的,如果勇士走到道具上,就会和npc重合。 +3. (懒人的选择)利用`图块类别:x,y`和`图块ID:x,y`等值块集中处理: + * 如果您实在搞不清楚“覆盖触发器”和“通行状态”这两项,那就干脆勾选前者并把后者设为不可通行,本希望可以通行的场合就得用“无视地形移动勇士”指令前进一步。 + * 用自动事件去转变图块,然后利用上述两个值块判定当前图块的状态等,再用“强制战斗”、“开门(需要钥匙)”和“增加道具”分别处理吧。 + +## 并行脚本:即时制的希望? + +如前所述,自动事件可以让您不用考虑可能导致各值块发生变化的缘由,而是简单地在刷新状态栏时检测这些值块是否满足某些特定的关系。 + +然而,自动事件依然是通过插入到当前待执行指令队列的开头,或追加到队列的结尾来执行的。换言之,它占用的是事件流本身的线程资源。 + +那形如bgs、bgv、飘云飘雾、地图背景旋转等即使玩家挂机也会照常执行的特效,该怎么办呢? + +js语言其实是没有多线程的,但我们可以写一些浏览器帧刷新时执行的脚本,也就是“并行脚本”。 + +并行脚本分为两种,全塔并行和楼层并行。前者在“脚本编辑—并行脚本”,后者在楼层属性。一般来说,当您有多个楼层需要执行相同的并行脚本时,建议写在前者并通过对`core.status.floorId`的范围进行判定(注意判空!)来决定具体执行的内容。 + +并行脚本将被系统反复执行,执行的时机是“浏览器帧刷新”,换言之相邻两次执行的间隔取决于浏览器或设备的性能,上限为60fps即每秒60次。 + +如果有一个bgs是1秒长的心跳声,我们把它注册到了全塔属性的音效中。假设这个bgs要被用于MT0层,那么我们在MT0层的“楼层属性——并行脚本”中写这样的代码: + +``` js +if (core.hasFlag('frame')) { + // 剧情事件中将“变量:frame”设为正整数来开启bgs,设为0来关闭 + core.status.hero.flags.frame %= 60000; // 防止挂机太久导致溢出 + if (core.getFlag('frame', 0) % 60 === 0) + core.playSound('heartBeat.mp3'); // 每60帧即1秒播放一次,还可以指定音调。 + core.status.hero.flags.frame++; // 帧数加1 +} // 其他特效也是一样的用法。 +``` + +在并行脚本(全塔或楼层)里可以使用`timestamp`作为参数,表示从游戏开始到当前总共过了多少毫秒。 + +## 覆盖楼传事件 + +对于特殊的塔,我们可以考虑修改楼传事件来完成一些特殊的要求,比如镜子可以按楼传来切换表里。 + +要修改楼传事件,需要进行如下两步: + +1. 重写楼传的点击事件。在插件中对`core.control.useFly`进行重写。详细代码参见[重写点击楼传事件](script#重写点击楼传事件)。 +2. 修改楼传的使用事件。和其他永久道具一样,在地图编辑器的图块属性中修改楼传的`useItemEvent`(或`useItemEffect`)和`canUseItemEffect`两个内容。例如: +``` js +"useItemEvent": "[...]" // 执行某段自定义事件 +"canUseItemEffect": "true" // 任何时候可用 +``` +3. 将全塔属性里的`楼梯边楼传`的勾去掉。 + +除了覆盖楼传事件外,对于快捷商店、虚拟键盘等等也可以进行覆盖,只不过是仿照上述代码重写对应的函数(`openQuickShop`,`openKeyBoard`)即可。 + +## 自定义快捷键 + +如果需要绑定某个快捷键为处理一段事件,也是可行的。 + +要修改按键,我们可以在脚本编辑的`onKeyUp`进行处理: + +我们设置一个快捷键进行绑定,比如 `Y`,其 `keycode` 是 `89` 。 +(大键盘数字键 `0-9` 的 `keycode` 为 `48-57, A-Z` 键的 `keycode` 为 `65-90` ,其他键的 `keycode` 搜一下就能得到) + +然后在脚本编辑的`onKeyUp`函数的`switch`中进行处理。 + +``` js +case 89: // 使用该按键的keyCode,比如Y键就是89 + // 还可以再判定altKey是否被按下,即 if (altKey) { ... + + // ... 在这里写你要执行脚本 + // **强烈建议所有新增的自定义快捷键均能给个对应的道具可点击,以方便手机端的行为** + if (core.hasItem('...')) { + core.status.route.push("key:0"); // 记录按键到录像中 + core.useItem('...', true); // 第二个参数true代表该次使用道具是被按键触发的,使用过程不计入录像 + } + + break; +``` +强烈建议所有新增的自定义非数字快捷键均给个对应的永久道具可点击,以方便手机端的行为。 + +使用`core.status.route.push("key:"+keyCode)`可以将这次按键记录在录像中。 + +!> 如果记录了按键,且使用道具的话,需要将useItem的第二个参数设为true,避免重复记录! + +可以使用`altKey`来判断Alt键是否被同时按下(V2.8起,手机端的Alt键以粘滞键方式提供)。 + +## 左手模式 + +V2.7.3起,样板面向玩家提供了“左手模式”,可在ESC菜单中开启。开启后,wsad将用于勇士移动,ijkl将代替原本wsad的功能(存读档等)。 + +## 插件系统 + +在H5中,提供了“插件”系统。在V2.6中提供了一个插件下拉框,用户可以自行创建和写插件。 + +在插件编写的过程中,我们可以使用任何[常见API](api)里面的代码调用;也可以通过`core.insertAction`来插入自定义事件执行。 + +下面是一个很简单的例子,我编写一个插件函数,其效果是让勇士生命值变成原来的x倍,并令面前的图块消失。 + +``` js +this.myfunc = function(x) { + core.status.hero.hp *= x; // 勇士生命翻若干倍 + core.insertAction([ // 自定义事件:令面前的图块消失。 + {"type": "setValue", "name": "flag:x", "value": "core.nextX()"}, + {"type": "setValue", "name": "flag:y", "value": "core.nextY()"}, + {"type": "hide", "loc": ["flag:x", "flag:y"]} + ]); +} +``` + +然后比如我们在某个道具的使用效果 `useItemEffect` 中写 `core.plugin.myfunc(2)` 即可调用此插件函数。也可以在战后事件或自定义脚本等位置来写。 + +网站上也提供了一个[插件库](https://h5mota.com/plugins/),欢迎大家把自己写的插件进行共享。 + +从V2.6开始,在插件中用`this.xxx`定义的函数将会被转发到core中。例如上述的`myfunc`除了`core.plugin.myfunc`外也可以直接`core.myfunc`调用。 + +详见[函数的转发](script#函数的转发)。 + +## 手机端按键模式 + +从V2.5.3以后,我们可以给手机端增加按键了,这样将非常有利于技能的释放。 + +用户在竖屏模式下点击难度标签,就会在工具栏按钮和快捷键模式之间进行切换。 + +切换到快捷键模式后,可以点1-7,分别等价于在电脑端按键1-7。 + +V2.8起,点击最后的A键则相当于电脑端按下/松开Alt键(即粘滞键),可以用来快捷换装。 + +可以在脚本编辑的`onKeyUp`中定义每个快捷键的使用效果,比如使用道具或释放技能等。 + +默认值下,1使用破,2使用炸,3使用飞,4使用其他存在的道具,5读取上一个自动存档,6读取下一个自动存档,7轻按。可以相应修改成自己的效果。 + +也可以替换icons.png中的对应图标,以及修改main.js中`main.statusBar.image.btn1~8`中的onclick事件来自定义按钮和对应按键。 + +非竖屏模式下、回放录像中、隐藏状态栏中,将不允许进行切换。 + +## 自绘状态栏 + +从V2.5.3开始允许自绘状态栏。要自绘状态栏,则应该打开全塔属性中的`statusCanvas`开关。 + +自绘模式下,全塔属性中的`statusCanvasRowsOnMobile`将控制竖屏模式下的状态栏行数(下面的`rows`)。 + +开启自绘模式后,可以在脚本编辑的`drawStatusBar`中自行进行绘制。 + +横屏模式下的状态栏为`129x416`(15x15则是`149x480`);竖屏模式下的状态栏为`416*(32*rows+9)`(15x15是480)。 + +具体可详见脚本编辑的`drawStatusBar`函数。 + +自绘状态栏开启后,金币图标将失去打开快捷商店的功能,您可以修改脚本编辑的 `onStatusBarCLick` 函数来适配。 + +## 自定义状态栏的显示项 + +在V2.2以后,我们可以自定义状态栏背景图(全塔属性 - 主样式)等等。 + +但是,如果我们还想新增其他项目的显示,比如攻速或者暴击,该怎么办? + +我们可以[自绘状态栏](#自绘状态栏),或者采用下面两个方式之一来新增。 + +### 利用已有项目 + +一个最为简单的方式是,直接利用已有项目。 + +例如,如果本塔中没有技能栏,则可以使用技能栏所对应的显示项。 + +1. 覆盖project/icons.png中技能的图标 +2. 打开全塔属性的enableSkill开关 +3. 在脚本编辑-updateStatusBar中可以直接替换技能栏的显示内容 + +``` + // 替换成你想显示的内容,比如你定义的一个flag:abc。 + core.setStatusBarInnerHTML('skill', core.getFlag("abc", 0)); +``` + +### 额外新增新项目 + +如果是在需要给状态栏新定义项目,则需要进行如下几个操作: + +1. 定义ID;比如攻速我就定义speed,暴击可以简单的定义baoji;你也可以定义其他的ID,但是不能和已有的重复。这里以speed为例。 +2. 在index.html的statusBar中,进行该状态栏项的定义。仿照其他几项,插在其应当显示的位置,注意替换掉相应的ID。 +``` html +
      + +

      +
      +``` +4. 使用便捷PS工具,打开project/icons.png,新增一行并将魔力的图标P上去;记下其索引比如37(从0开始数)。 +5. 在main.js的this.statusBar中增加图片、图标和内容的定义。 +``` js +this.statusBar = { + 'images': { + // ...其他略 + 'speed': document.getElementById("img-speed"), // 图片的定义 + }, + 'icons': { + // ...其他略 + 'speed': 37, // 图标的定义,这里对应的是icons.png中的索引 + }, + // ...其他略 + 'speed': document.getElementById('speed'), // 显示内容(数据)的定义 +} +``` +6. 显示内容的设置。在脚本编辑的updateStatusBar函数,可以对该状态栏显示内容进行设置,下面是几个例子。 +``` js +// 设置其显示内容为status:speed值;需要在project/data.js中firstData的hero那里新增初始值`"speed": 0`。 +core.setStatusBarInnerHTML('speed', core.getStatus('speed')); +// 设置其显示内容为flag:speed值,无需额外进行定义。 +core.setStatusBarInnerHTML('speed', core.getFlag('speed', 0)); +``` +总的来说不建议这样做,因为`main.js`和各种html文件不在`project`文件夹,会导致随样板更新迁移接档变得困难。 + +## 技能塔的支持 + +从V2.5开始,内置了"二倍斩"技能,可以仿照其制作自己的技能。要支持技能塔,可能需要如下几个方面: + +- 魔力(和上限)的添加;技能的定义 +- 状态栏的显示 +- 技能的触发(按键与录像问题) +- 技能的效果 + +### 魔力的定义添加;技能的定义 + +从V2.5开始,提供了 `status:mana` 选项,可以直接代表当前魔力值。可以在全塔属性勾选来启用它。 + +如果需要魔力上限,则可以使用 `status:manaMax` 来表示当前的魔力最大值,负数表示没有上限。 + +同时,我们可以使用flag:skill表示当前开启的技能编号,flag:skillName表示当前开启的技能名称。 + +如果flag:skill不为0,则代表当前处于某个技能开启状态,且状态栏显示flag:skillName值。伤害计算函数中只需要对flag:skill进行处理即可。 + +### 状态栏的显示 + +从V2.5开始,魔力值和技能名的状态栏项目已经被添加,可以直接使用。 + +在脚本编辑-updateStatusBar中,可以对状态栏显示内容进行修改。带有上限时如果一行放不下,可以想办法像生命一样拆成两行。 + +``` js +// 设置魔力值; status:manamax 只有在非负时才生效。 +if (core.status.hero.manamax != null && core.status.hero.manamax >= 0) { + core.status.hero.mana = Math.min(core.status.hero.mana, core.status.hero.manamax); + core.setStatusBarInnerHTML('mana', core.status.hero.mana + "/" + core.status.hero.manamax); +} +else { + core.setStatusBarInnerHTML("mana", core.status.hero.mana); +} +// 设置技能栏 +// 可以用flag:skill表示当前开启的技能类型,flag:skillName显示技能名 +core.setStatusBarInnerHTML('skill', core.getFlag('skillName', '无')); +``` + +### 技能的触发 + +#### 使用道具作为技能 + +由于手机端按字母键不方便,虚拟键盘不好用,因此强烈推荐**给每个字母键技能设置一个道具,在道具栏点击使用!** + +下面是个很简单的例子,要制作一个技能"二倍斩"。 + +我们可以设置一个道具,其cls是`constants`(永久道具),ID比如是`skill1`。 + +该道具的使用判定`canUseItemEffect`是`true`(表示任意时候都可使用),使用效果`useItemEffect`是: + +``` js +if (core.getFlag('skill', 0)==0) { // 判断当前是否已经开了技能 + if (core.getStatus('mana')>=5) { // 这里要写当前能否开技能的条件判断,比如魔力值至少要多少 + core.setFlag('skill', 1); // 开技能1 + core.setFlag('skillName', '二倍斩'); // 设置技能名 + } + else { + core.drawTip("魔力不足,无法开技能"); + } +} +else { // 关闭技能 + core.setFlag('skill', 0); // 关闭技能状态 + core.setFlag('skillName', '无'); +} +``` + +简单的说,用flag:skill判断当前开启的技能,flag:skillName表示该技能名。(可在状态栏显示) + +该(技能)道具任何时候都可被使用;使用时,判断当前是否开启了技能,如果开启则关闭,没开则再判断是否允许开启(魔力值够不够等)。 + +V2.6.6起,道具提供了`useItemEvent`项,建议使用它而不是上面的脚本。'- + +#### 快捷键触发技能 + +在PC端,我们还可以按键触发技能。 + +在技能的道具定义完毕后,再将该道具绑定到一个快捷键上。有关绑定按键请参见[自定义快捷键](#自定义快捷键)。 + +下面是一个很简单的例子,当勇士按下 `F` 后,触发我们上面定义的二倍斩技能。 +``` js +case 70: // F:开启技能“二倍斩” + // 是否拥有“二倍斩”这个技能道具 + if (core.hasItem('skill1')) { + core.status.route.push("key:70"); + core.useItem('skill1', true); + } + break; +``` +在勇士处于停止的条件下,按下 `F` 键时,判断技能的道具是否存在,如果存在再使用它。 + +!> 由于现在手机端存在拓展键盘,也强烈建议直接覆盖1-7的使用效果,这样手机端使用也非常方便。 + +### 技能的效果 + +最后一点就是技能的效果;其实到了这里就和RM差不多了。 + +技能的效果要分的话有地图类技能,战斗效果类技能,后续影响类技能什么的,这里只介绍最简单的战斗效果类技能。 + +其他的几类技能根据需求可能更为麻烦,有兴趣可自行进行研究。 + +战斗效果内技能要改两个地方:战斗伤害计算,战后扣除魔力值。 + +战斗伤害计算在脚本编辑的`getDamageInfo`函数,有需求直接修改这个函数即可。 + +战后扣除魔力值则在脚本编辑的`afterBattle`中进行编辑即可。 + +举个例子,我设置一个勇士的技能:二倍斩,开启技能消耗5点魔力,下一场战斗攻击力翻倍。 + +那么,直接在脚本编辑的`getDamageInfo`中进行判断: + +``` js +if (core.getFlag('skill', 0)==1) { // 开启了技能1 + hero_atk *= 2; // 计算时攻击力翻倍 +} +``` + +然后在脚本编辑的`afterBattle`中进行魔力值的扣除: + +``` js +// 战后的技能处理,比如扣除魔力值 +if (core.flags.statusBarItems.indexOf('enableSkill')>=0) { + // 检测当前开启的技能类型 + var skill = core.getFlag('skill', 0); + if (skill==1) { // 技能1:二倍斩 + core.status.hero.mana-=5; // 扣除5点魔力值 + } + // 关闭技能 + core.setFlag('skill', 0); + core.setFlag('skillName', '无'); +} +``` + +!> 开启技能后,建议将全塔属性的`useLoop`置为`true`,即改用循环计算临界值,这样临界计算才不会出问题! + +  + +通过上述这几种方式,我们就能成功的让H5支持技能啦! + +## 系统使用的flag变量 + +众所周知,自定义flag变量都可以任意定义并取用(未定义直接取用的flag默认值为0)。 + +下面是一些可能会被系统设置或取用的flag变量: + +- **`flag:hard`**: 当前的难度标志;此flag变量在setInitData中被定义,可以直接取用来判定当前难度分歧。上传成绩时将根据此flag来对不同难度进行排序。 +- **`flag:posion`**, **`flag:weak`**, **`flag:curse`**: 中毒、衰弱、诅咒状态。 +- **`flag:no_zone`**, **`flag:no_repulse`**, **`flag:no_laser`**, **`flag:no_betweenAttack`**: 是否分别免疫领域、阻击、激光、夹击效果。 +- **`flag:hatred`**: 当前的仇恨数值。 +- **`flag:commonTimes`**: 全局商店共用次数时的访问次数。 +- **`flag:input`**: 接受用户输入的事件后,存放用户输入的结果。 +- **`flag:type`**, **`flag:keycode`**, **`flag:x`**, **`flag:y`**, **`flag:px`**, **`flag:py`**: 等待用户操作后,用户的操作类型,按键keycode或点击/像素坐标。 +- **`flag:skill`**, **`flag:skillName`**: 开启的技能编号和技能名。 +- **`flag:saveEquips`**: 快速换装时保存的套装。 +- **`flag:__visited__`**: 当前访问过的楼层。 +- **`flag:__atk_buff__`**, **`flag:__def_buff__`**, **`flag:__mdef_buff__`**: 当前攻防护盾的实际计算比例加成。 +- **`flag:__color__`**, **`flag:__weather__`**, **`flag:__volume__`**: 当前的画面色调、天气和音量。 +- **`flag:__events__`**: 当前保存的事件列表,读档时会恢复(适用于在事件中存档) +- **`flag:textAttribute`**, **`flag:globalAttribute`**, **`flag:globalFlags`**: 当前的剧情文本属性,当前的全局属性,当前的全局开关。 +- **`flag:cannotMoveDirectly`**, **`flag:__noClickMove__`**: 当前是否不允许瞬间移动,当前用户是否开启了单击瞬移。 +- **`flag:hideStatusBar`**, **`flag:showToolbox`**: 是否隐藏状态栏,是否显示工具栏。 +- **`flag:debug`**: 当前是否开启了调试模式。 +- **`flag:__seed__`**, **`flag:__rand__`**: 伪随机数生成种子和当前的状态 + +========================================================================================== + +[继续阅读脚本](script) diff --git a/_docs/script.md b/_docs/script.md new file mode 100644 index 0000000..64e7255 --- /dev/null +++ b/_docs/script.md @@ -0,0 +1,739 @@ +# 脚本 + +?> 在这一节中,让我们来了解如何使用控制台和使用脚本! + +在V2.6版本中,基本对整个项目代码进行了重写,更加方便造塔者的使用。 + +* [脚本教程视频](https://www.bilibili.com/video/BV1uL411J7yZ?share_source=copy_web) + +## 控制台的使用 + +在Chrome浏览器中,按(Ctrl+Shift+I)可打开控制台。 + +![](img/console.jpg) + +控制台中有很多的标签,最常用的是`Console`, `Sources`和`Elements`。 + +有关更详尽的控制台使用可自行搜索[Chrome开发者工具](https://www.baidu.com/s?wd=chrome%20%E5%BC%80%E5%8F%91%E8%80%85%E5%B7%A5%E5%85%B7)了解更多。 + +### Console:命令行 + +Console页为命令行。可以在这里输入一些命令进行调试。 + +比如,进入游戏后,输入`core.status.hero.atk`即可获得勇士的当前攻击力数值。`core.status.hero.atk=100`可以设置攻击力为100。 + +更多的API可参见[附录:API列表](#附录:API列表)。 + +除此以外,游戏中的报错等信息也是可以在Console中进行查看的。 + +![](img/console1.jpg) + +### Sources:断点调试 + +Sources页可以查看JS源代码,并进行断点调试等。 + +例如,如果相对脚本编辑中的伤害计算函数进行断点调试: +1. 在左边找到`project/functions.js`,单击打开文件 +2. 并找到对应的行(可以Ctrl+F搜索),比如搜索`getDamageInfo` +3. 在行号上点一下打断点,会出现一个蓝色标签 + +之后,当代码运行到你的断点处时,将自动停止运行。 + +![](img/sources.jpg) + +可以将鼠标移动到变量上,将弹窗形式显示这个变量的各项数值,从而查看变量值是否符合预期。 + +图中红色框内有几个按钮,从左到右分别是:**继续执行**,**执行到下一行**,**进入当前函数**,**跳出当前函数**,**单步执行**。 + +通过这几个按钮,可以一行一行的对代码进行执行,执行过程中能不断查看各个变量的数值变化,从而定位问题所在。 + +红圈下方是Call Stack,即当前的函数调用链(从哪些地方调用过来的)。 + +Sources还有更多有趣的功能,在此不做介绍,有兴趣的可自行网上搜索了解。 + +### Elements:网页元素查看 + +Elements页可以查看网页的源代码,调整css布局等。 + +![](img/elements.jpg) + +不过对魔塔样板来说,最重要的是红圈中的按钮。点击此按钮可以进入**手机模式**。 + +手机模式下,左边可以对屏幕分辨率进行调整和模拟。 + +这可以很有效的帮我们进行测试样板在手机端的表现。 + +## 整体项目架构 + +``` text +├── /_server/ # 为可视化地图编辑器提供一些支持的目录 +├── /libs/ # ---- 系统库目录 ---- +│ ├─ /thirdparty/ # 游戏所用到的第三方库文件 +│ ├─ actions.js # 用户交互处理 +│ ├─ core.js # 系统核心文件(游戏入口,接口&转发) +│ ├─ control.js # 游戏逻辑控制 +│ ├─ data.js # 全塔属性等 +│ ├─ enemys.js # 怪物相关处理 +│ ├─ events.js # 各个事件的执行 +│ ├─ icons.js # 图标和素材 +│ ├─ items.js # 道具效果 +│ ├─ loader.js # 各个资源加载 +│ ├─ maps.js # 地图数据和绘制 +│ ├─ ui.js # UI窗口绘制 +│ └─ utils.js # 工具类函数 +├── /project/ # ---- 项目目录 ---- +│ ├─ /animates/ # 动画目录 +│ ├─ /floors/ # 楼层文件 +│ ├─ /images/ # 图片素材 +│ ├─ /sounds/ # bgm和音效 +│ ├─ data.js # 全塔属性 +│ ├─ enemys.js # 怪物属性 +│ ├─ events.js # 公共事件 +│ ├─ functions.js # 脚本编辑 +│ ├─ icons.js # 素材和ID的对应关系定义 +│ ├─ items.js # 道具的定义和效果 +│ ├─ maps.js # 地图和数字的对应关系 +│ └─ plugins.js # 自定义插件 +├── /常用工具/ # 辅助造塔的小工具 +├── editor.html # 地图编辑器 +├── editor-mobile.html # 手机版的地图编辑器 +├── index.html # 主程序,游戏的入口 +├── main.js # JS程序的入口,将动态对所需JS进行加载 +├── style.css # 游戏所需要用到的样式表 +└── 启动服务.exe # 一个本地的HTTP服务器,通过它来运行游戏 +``` + +`_server`为**地图编辑器目录**,里面存放了地图编辑器相关的各项内容。 + +`libs`为**系统库目录**,里面存放了各个系统核心函数。 + +从V2.6开始,请勿直接修改libs下的代码,如有需要修改系统库函数请尝试在插件中[复写函数](#复写函数)。 + +`project`为**项目目录**,你所造的塔的数据全部存放在project下。在不同样板之间接档也是直接迁移project目录即可。 + +## 函数的转发 + +在本样板中,`core.js`里面基本是没有定义什么函数的,所有的游戏内函数都在其他几个文件中实现。 + +例如,常见的获得某个变量值`getFlag`是定义在`control.js`中的: + +```js +////// 获得某个自定义变量或flag ////// +control.prototype.getFlag = function(name, defaultValue) { + if (!core.status.hero) return defaultValue; + var value = core.status.hero.flags[name]; + return value != null ? value : defaultValue; +} +``` + +也就是,我们可以通过`core.control.getFlag(name, value)`来调用此函数。 + +但是这样会十分不便,我们希望能直接调用`core.getFlag(name, value)`,而不需要中间的control。 + +为了达到这个目的,样板设置了**函数转发**,即**将其他文件中定义的函数,转发到core中执行**。 + +上述`getFlag`代码的转发实际上是增加了如下函数: + +```js +////// getFlag函数的转发 ////// +core.getFlag = function (name, defaultValue) { + return core.control.getFlag(name, defaultValue); +} +// 转发后,即可通过 core.getFlag() 来实际调用 core.control.getFlag() +``` + +转发是自动完成的,其满足如下两条规则: +- **在libs中其他文件定义的函数,如果不以下划线`_`开头,就会进行转发。** +- **如果core中已经存在同名函数,则会在控制台中打出一条报错信息,并不转发该函数。** + +具体函数的转发实现代码可参见`core.js`的`_forwardFunc`函数。 + +!> 除此以外,插件中以`this.xxx`来定义的函数也会被转发! + +例如,你可以直接调用`core.drawLight()`来实际调用插件中的`core.plugin.drawLight`。 + +## 插件编写 + +插件编写是H5魔塔的一个重大特点,从V2.0.1引入,并逐渐发扬光大。 + +对于有一定脚本经验的人来说,可以编写插件来实现各种各样的功能,包括且不仅限于拓展功能的实现,系统代码的复写等等。 + +在V2.5.5以前,插件位置都在脚本编辑中;从V2.6开始则迁移到了新的下拉框中,并进行了切分。 + +你也可以创建自己的插件。 + +![](img/plugin.jpg) + +新的插件切分和原来的单插件使用方法完全一致,单纯进行了切分而已。可参见已有的`init`和`drawLight`的样例。 + +拆分的意义主要是将各个可能的功能独立出来,避免单个框内内容太长,过大和混杂等。 + +在V2.6中,应当每个独立的额外功能实现都新建一个自己的插件,这样也方便进行拓展,例如打包迁移到别的塔上,或发布在网页插件库中。 + +另外一点需要注意的是,所有插件的初始化都会在系统资源加载之前,此时图片等资源尚未进行加载。 + +在所有资源加载完毕时,将会执行init插件中的_afterLoadResources函数,可以在这里对资源进行一些操作,比如切分图片等。 + +```js +function () { + console.log("插件编写测试"); + + // 可以写一些直接执行的代码 + // 在这里写的代码将会在【资源加载前】被执行,此时图片等资源尚未被加载。 + // 请勿在这里对包括bgm,图片等资源进行操作。 + + + this._afterLoadResources = function () { + // 本函数将在所有资源加载完毕后,游戏开启前被执行 + // 可以在这个函数里面对资源进行一些操作,比如切分图片等。 + + // 这是一个将assets.png拆分成若干个32x32像素的小图片并保存的样例。 + // var arr = core.splitImage("assets.png", 32, 32); + // for (var i = 0; i < arr.length; i++) { + // core.material.images.images["asset"+i+".png"] = arr[i]; + // } + + } + + // 可以在任何地方(如afterXXX或自定义脚本事件)调用函数,方法为 core.plugin.xxx(); + // 从V2.6开始,插件中用this.XXX方式定义的函数也会被转发到core中,详见文档-脚本-函数的转发。 +} +``` + +网站上提供了一个插件库,[https://h5mota.com/plugins/](https://h5mota.com/plugins/),上面有一些大家分享的插件,可供使用。 + +可以查看附录中的[API列表](api)来查看所有的系统API内容。 + +## register系列函数 + +在API列表中,有一系列的函数以`register`开头;这一系列函数允许你将一部分自定义的代码注入到样板中,从而可以监听某一系列行为并进行处理。 + +下面会对这系列函数进行逐个介绍。 + +### registerAction + +``` +registerAction: fn(action: string, name: string, func: string|fn(params: ?), priority?: number) +此函数将注册一个用户交互行为。 +action: 要注册的交互类型,如 ondown, onup, keyDown 等等。 +name: 你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。 +func: 执行函数。 +如果func返回true,则不会再继续执行其他的交互函数;否则会继续执行其他的交互函数。 +priority: 优先级;优先级高的将会被执行。此项可不填,默认为0 +``` + +`registerAction`为register系列最常用的功能之一,它允许你任意监听用户的交互事件,包括且不仅限于按键、点击、拖动、长按,等等。 + +下面是一个例子: + +```js +// 当flag:abc是true时,点击屏幕左上角可以使用道具破墙镐 +// 注入一个 ondown 事件,名称为 my_pickaxe +core.registerAction('ondown', 'my_pickaxe', function (x, y, px, py) { + // 如果当前正在执行某个事件,则忽略之。 + if (core.status.lockControl) return false; + // 如果勇士正在行走中,则忽略之。 + if (core.isMoving()) return false; + // x, y 为点击坐标;px, py 为点击的像素坐标 + // 检查flag是否为true,且点击了(0,0)点 + if (core.hasFlag('abc') && x == 0 && y == 0) { + // 检查能否使用破墙镐 + if (core.canUseItem('pickaxe')) { + core.useItem('pickaxe'); + // 返回true将拦截其他交互函数对于点击的处理 + return true; + } + } + // 返回false后将会继续执行其他的交互函数(例如寻路) + return false; + +// 优先级设置为100,比系统优先级高,因此将会优先执行此注入的项目。 +}, 100); + +// 当你不再需要上述监听时,可以通过下面这一句取消注入。 +// core.unregisterActon('ondown', 'my_pickaxe'); +``` + +下面是另一个例子: + +```js +// 假设我们通过事件流做了一个商店,希望能通过”长按空格“和“长按屏幕”进行连续确认购买 +// 此时,需要使用一个flag,表示是否在商店中;例如 flag:inShop + +// 注入一个keyDown(按下)事件;请注意,当按下按键且不释放时,会连续触发keyDown。 +core.registerAction('keyDown', 'my_shop', function (keycode) { + // 检查是否确实在该商店事件中;进该商店前需要将flag:inShop设为true,退出后需要设为false + if (!core.hasFlag('inShop')) return false; + // 检查当前事件是一个”显示选择项“ + if (core.status.event.id != 'action' || core.status.event.data.type != 'choices') { + return false; + } + // 获得当前“显示选择项”的各个选项 + var choices = core.status.event.data.current.choices; + // 获得当前选项第一项在屏幕上的y坐标 + var topIndex = core.actions._getChoicesTopIndex(choices.length); + // 检查按键的键值,是否是空格或者回车 + if (keycode == 13 || keycode == 32) { + // 如果是,则视为直接点击屏幕上的选项 + // core.status.event.selection为当前光标选择项;第一项为0 + // 点击的x为HSIZE,即屏幕左右居中;y为topIndex + core.status.event.selection为该选择项 + core.actions._clickAction(core.actions.HSIZE, topIndex + core.status.event.selection); + // 不再执行其他的交互函数 + return true; + } + // 否则,继续执行其他的交互函数(如上下键光标选择) + return false; +}, 100); + + +// 还需要注册一个keyUp(放开)事件,拦截空格和回车 +// 这是因为,按一下空格会先触发keyDown再触发keyUp;这里不拦截的话会购买两次。 +var _my_shop_up = function (keycode) { + if (!core.hasFlag('inShop')) return false; + if (core.status.event.id != 'action' || core.status.event.data.type != 'choices') { + return false; + } + // 检查是否是空格或者回车,如果是则拦截操作。 + if (keycode == 13 || keycode == 32) return true; + return false; +}; +// 这里可以传入前面定义的函数体。 +core.registerAction('keyUp', 'my_shop', _my_shop_up, 100); + + +// 注册一个长按事件,允许长按连续购买 +this._my_shop_longClick = function (x, y, px, py) { + if (!core.hasFlag('inShop')) return false; + if (core.status.event.id != 'action' || core.status.event.data.type != 'choices') { + return false; + } + // 直接视为点击了此位置 + core.actions._clickAction(x, y, px, py); + return true; +} +// 如果是插件中使用`this.xxx`定义的函数,也可以直接传入函数名。 +core.registerAction('longClick', 'my_shop', '_my_shop_longClick', 100); +``` + +简单地说,`registerAction`这个函数**允许你在任何时候监听和拦截任何用户交互行为**。 + +目前,`registerAction`支持监听和拦截如下交互: + +- `keyDown`: 当一个键被按下时 + - 对应的函数参数:`function (keycode)`,为此时按下键的键值 + - 请注意:如果长按键不放开的话,会连续触发`keyDown`事件 +- `keyUp`: 当一个键被放开时 + - 对应的函数参数:`function (keycode, altKey, fromReplay)`,为此时放开键的键值、当前alt是否被按下、是否是录像播放中模拟的按键 +- `onKeyDown`: 当一个键被按下时 + - 对应的函数参数:`function (e)`,为此时按键的信息。这里的`e`是一个`KeyboardEvent`。 + - 请注意:如果长按键不放开的话,会连续触发`onKeyDown`事件。 + - 和上述`keyDown`的主要区别就是参数为原始的`KeyboardEvent`;仍然推荐直接使用`keyDown`。 +- `onKeyUp`: 当一个键被放开时 + - 对应的函数参数:`function (e)`,为此时按键的信息。这里的`e`是一个`KeyboardEvent`。 + - 和上述`keyUp`的主要区别就是参数为原始的`KeyboardEvent`;仍然推荐直接使用`keyUp`。 +- `ondown`: 当屏幕被鼠标或手指按下时 + - 对应的函数参数:`function (x, y, px, py)`,为此时按下时的格子坐标和像素坐标。 +- `onmove`: 当屏幕被鼠标滑动,或手指拖动时 + - 对应的函数参数:`function (x, y, px, py)`,为此时滑动或拖动时的格子坐标和像素坐标。 + - 如果是鼠标,只要在屏幕上滑动就会触发`onmove`,无需处于点击状态(也就是悬浮也是可以的)。 + - 如果是触摸屏,则只有手指按下滑动时才会触发`onmove`(并不存在什么悬浮的说法)。 +- `onup`: 当屏幕被鼠标或手指放开时 + - 对应的函数参数:`function (x, y, px, py)`,为此时放开时的格子坐标和像素坐标。 +- `onclick` 【已废弃】 + - 从V2.8.2起,此交互已被废弃,注册一个`onclick`会被直接转发至`ondown`。 +- `onmousewheel`: 当鼠标滚轮滚动时 + - 对应的函数参数:`function (direct)`,为此时滚轮方向。向下滚动是-1,向上滚动是1。 + - 目前在楼传、怪物手册、存读档、浏览地图等多个地方绑定了鼠标滚轮事件。 +- `keyDownCtrl`: 当Ctrl键处于被长按不放时 + - 对应的函数参数:`function ()`,即无参数。 + - 目前主要用于跳过剧情对话。 +- `longClick`: 当鼠标或手指长按屏幕时 + - 对应的函数参数:`function (x, y, px, py)`,为长按屏幕对应的格子坐标和像素坐标 + - 目前主要用于虚拟键盘,和跳过剧情对话。 +- `onStatusBarClick`: 当点击状态栏时 + - 对应的函数参数:`function (px, py, vertical)`,为状态栏点击的像素坐标。 + - 如果参数`vertical`不为`null`,表示该状态栏点击是录像播放时触发的,其值为录像记录时的横竖屏状态。 + +### registerAnimationFrame + +``` +registerAnimationFrame: fn(name: string, needPlaying: bool, func?: fn(timestamp: number)) +注册一个 animationFrame +name: 名称,可用来作为注销使用 +needPlaying: 是否只在游戏运行时才执行(在标题界面不执行) +func: 要执行的函数,或插件中的函数名;可接受timestamp(从页面加载完毕到当前所经过的时间)作为参数 +``` + +`registerAnimationFrame`为其次常用的功能,可以用于需要反复执行的脚本,常用于动画之类。 + +目前,样板的全局动画(即怪物的帧振动)、人物行走动画、普通动画、天气效果、游戏计时、以及楼层并行事件等,都是通过`registerAnimationFrame`进行实现的。 + +通过`registerAnimationFrame`注册的函数会平均每16.6ms执行一次(视浏览器的性能而定,例如手机上可能执行频率会变慢)。可以通过函数的`timestamp`参数来获得从页面加载完毕到当前所经过的毫秒数(从而可以进一步控制执行频率,如每500ms才执行一次)。 + +**推荐所有的需要反复执行的脚本(如动画相关)都通过此函数来注册,而不是使用`setInterval`,以获得更好的性能。** + +下面是“人物血量动态变化”的插件的例子。 + +```js +var speed = 0.05; // 动态血量变化速度,越大越快。 +var _currentHp = null; // 当前正在变化的hp值 +var _lastStatus = null; // 上次人物的属性对象 + +// 注册一个 +core.registerAnimationFrame('dynamicHp', true, function (timestamp) { + // 检查人物所指向的对象是否发生了变化 + // 在例如读档后,core.status.hero被重置了,此时需要将_currentHp重置为正确的生命值。 + if (_lastStatus != core.status.hero) { + _lastStatus = core.status.hero; + _currentHp = core.status.hero.hp; + } + // 如果显示的hp值和当前实际hp值不同 + if (core.status.hero.hp != _currentHp) { + // 计算hp差值,并获得下个血量变化值。 + var dis = (_currentHp - core.status.hero.hp) * speed; + if (Math.abs(dis) < 2) { + // 如果很接近了,直接设为实际hp值。 + _currentHp = core.status.hero.hp; + } else { + // 否则,改变显示值使其更接近。 + _currentHp -= dis; + } + // 然后设置状态栏中生命的显示项。 + core.setStatusBarInnerHTML('hp', _currentHp); + } +}); +``` + +### registerReplayAction + +``` +registerReplayAction: fn(name: string, func: fn(action?: string) -> bool) +注册一个录像行为 +name: 自定义名称,可用于注销使用 +func: 具体执行录像的函数,可为一个函数或插件中的函数名; +需要接受一个action参数,代表录像回放时的下一个操作 +func返回true代表成功处理了此录像行为,false代表没有处理此录像行为。 +``` + +`registerReplayAction`允许你自己定义和注册一个录像行为。 + +在游戏时,你的所有操作会被记录为一个数组`core.status.route`,如按上就是`up`,使用破墙镐就是`item:pickaxe`,瞬移到(1,2)就是`move:1:2`,等等。 + +在录像播放时,会依次从数组中取出一个个的录像操作,并模拟执行对应的操作。这里对于每个录像操作,都是靠`registerReplayAction`进行注册的。 + +下面是一个例子: + +```js +// 当flag:abc为true时,点击屏幕(0,0),(0,1),(0,2),(0,3)会分别执行不同的公共事件。 + +// 先通过registerAction注册屏幕点击事件 +core.registerAction('onclick', 'commonEvent', function (x, y, px, py) { + // 检查是否是自由状态、是否在移动,是否存在flag。 + if (core.status.lockControl || core.isMoving() || !core.hasFlag('abc')) return false; + // 公共事件名 + var eventName = null; + if (x == 0 && y == 0) { + eventName = "公共事件0"; + } else if (x == 0 && y == 1) { + eventName = "公共事件1"; + } else if (x == 0 && y == 2) { + eventName = "公共事件2"; + } // ... 可以继续写下去。 + + // 如果存在需要执行的公共事件 + if (eventName != null) { + // 重要!往录像中写入一个自定义的`commonEvent:`项,记录执行的公共事件名称。 + // 由于录像编码时只支持部分ascii码,而eventName作为公共事件名可能带有中文, + // 因此需要使用 core.encodeBase64() 对eventName进行编码 + core.status.route.push("commonEvent:" + core.encodeBase64(eventName)); + // 插入公共事件 + core.insertCommonEvent(eventName); + return true; + } + return false; +}); + +// 注册一个录像处理行为来处理上述的`commonEvent:`项。 +core.registerReplayAction('commonEvent', function (action) { + // 参数action为此时录像播放时执行到的项目。 + // 如果不是`commonEvent:`,则不应该被此函数处理。 + if (action.indexOf('commonEvent:') !== 0) return false; + // 二次检查flag:abc是否存在 + if (!core.hasFlag('abc')) { + core.control._replay_error(action); + return true; + } + // 如果是我们的录像行为,获得具体的公共事件名。 + // 上面使用encodeBase64()编码了录像,现在同样需要解码。 + var eventName = core.decodeBase64(action.substring(12)); + // 由于录像播放中存在二次记录,还需要将action写入到当前播放过程中的录像中。 + // 这样录像播放完毕后再次直接重头播放不会出现问题。 + core.status.route.push(action); + // 执行该公共事件。 + core.insertCommonEvent(eventName); + // 继续播放录像。 + core.replay(); + // 返回true表示我们已经成功处理了此录像行为。 + return true; +}); +``` + +### registerWeather + +``` +registerWeather: fn(name: string, initFunc: fn(level: number), frameFunc?: fn(timestamp: number, level: number)) +注册一个天气 +name: 要注册的天气名 +initFunc: 当切换到此天气时的初始化;接受level(天气等级)为参数;可用于创建多个节点(如初始化雪花) +frameFunc: 每帧的天气效果变化;可接受timestamp(从页面加载完毕到当前所经过的时间)和level(天气等级)作为参数 +天气应当仅在weather层进行绘制,推荐使用core.animateFrame.weather.nodes用于节点信息。 +``` + +`registerWeather`允许你注册一个天气。 + +在游戏时,楼层属性中可以设置天气如 `["snow", 5]`,或者脚本 `core.setWeather("snow", 5)` 来切换天气。 + +下面是一个例子: + +```js +// 注册一个”血“天气,每200ms就随机在界面上的绘制红色斑点 +core.registerWeather('blood', function (level) { + // 切换到此天气时应当执行的脚本吗,如播放一个音效 + core.playSound('blood.mp3'); +}, function (timestamp, level) { + // 我们希望每200ms就界面上随机绘制 level^2 个红点,半径在0~32像素之间 + + // 检查是否经过了200ms + if (timestamp - core.animateFrame.weather.time < 200) return; + // 当且仅当在weather层上绘制 + core.clearMap('weather'); + for (var i = 0; i < level * level; ++i) { + // 随机界面中的一个点,半径在0~32之间 + var px = Math.random() * core.__PIXELS__; + var py = Math.random() * core.__PIXELS__; + var r = Math.random() * 32; + core.fillCircle('weather', px, py, r, 'red'); + } + // 设置本次天气调用的时间 + core.animateFrame.weather.time = timestamp; +}); +``` + +值得注意的是,天气当且仅当在`weather`层进行绘制,推荐使用或设置`core.animateFrame.weather.time`作为上次天气调用的时间避免太过于频繁的调用。 + +推荐使用`core.animateFrame.weather.nodes`来存储天气的节点,这样会在取消天气时自动被移除。 + +样板的云`cloud`和雾`fog`均由多个图片叠加移动实现;如果你想实现类似效果,可直接使用`core.control.__animateFrame_weather_image`作为`frameFunc`,详见样板的云雾实现。 + +另外注意的是,注册的天气无法在事件编辑器的下拉框中选择;你可以选择脚本调用`core.setWeather`,或者修改`_server/MotaAction.g4`中的`Weather_List`: + +```js +Weather_List + : '无'|'雨'|'雪'|'晴'|'雾'|'云' + /*Weather_List ['null','rain','snow','sun','fog','cloud']*/; +``` + +### registerSystemEvent + +``` +registerSystemEvent: fn(type: string, func: fn(data?: ?, callback?: fn())) +注册一个系统事件 +type: 事件名 +func: 为事件的处理函数,可接受(data,callback)参数 +``` + +`registerSystemEvent`允许你注册一个系统事件。 + +在图块属性中,存在一个`trigger`选项,可以设置图块的触发器;当玩家碰触到对应的图块时,会根据对应图块的触发器来执行对应系统事件,即`registerSystemEvent`所注册的。 + +这里的`data`为触发该图块时的`block`信息,和`core.getBlock()`的返回值相同。`callback`为执行完毕时的回调。 + +下面是一个例子: + +```js +// 假设存在一个图块,当触碰时会触发一个公共事件,且需要图块坐标作为公共事件参数。 +// 首先需要将该图块的触发器设置为`custom`。 + +// 注册一个`custom`的系统事件;当角色碰触到触发器为`custom`的图块时会被执行。 +core.registerSystemEvent("custom", function (data, callback) { + // 这里的`data`为碰触到的图块信息。 + console.log(data); + // 插入一个公共事件(如“图块触碰”),把图块坐标作为公共事件参数传入。 + core.insertCommonEvent("图块触碰", /*args*/ [data.x, data.y], data.x, data.y); + if (callback) callback(); +}) +``` + +`registerSystemEvent`系列最大的好处是,他是和“图块”相关而不是和“点”相关的。也就是说,可以任意通过`setBlock`之类的函数移动图块,不会影响到事件的触发(而这个是靠红点所无法完成的)。 + +也可以通过`registerSystemEvent`来拦截默认的系统事件,例如 `core.registerSystemEvent("battle", ...)` 可以拦截当遇到没有覆盖触发器的怪物的系统处理(即不再直接战斗)。 + +### registerEvent + +``` +registerEvent: fn(type: string, func: fn(data: ?, x?: number, y?: number, prefix?: string)) +注册一个自定义事件 +type: 事件类型 +func: 事件的处理函数,可接受(data, x, y, prefix)参数 +data为事件内容,x和y为当前点坐标(可为null),prefix为当前点前缀 +``` + +`registerEvent`允许你注册一个自定义事件,即事件流中 type:xxx 的行为。 + +```js +// 注册一个type:abc事件 +core.registerEvent("abc", function (data, x, y, prefix) { + // 直接打出当前事件信息 + console.log(data.name); + core.doAction(); +}); + +// 执行一个abc事件 +// 控制台会打出 "hahaha" +// core.insertAction([{"type": "abc", "name": "hahaha"}]); +``` + +此函数一般不是很常用,但是配合“编辑器修改”(即给事件编辑器修改或者增加事件图块)会有奇效。 + +具体例子可参见[修改编辑器](editor)中的`demo - 增加新语句图块`。 + +### registerResize + +``` +registerResize: fn(name: string, func: fn(obj: ?)) +注册一个resize函数 +name: 名称,可供注销使用 +func: 可以是一个函数,或者是插件中的函数名;可以接受obj参数,详见resize函数。 +``` + +`registerResize`允许你重写一个屏幕重定位函数。 + +此函数会在游戏开始前,以及每次屏幕大小发生改变时调用。系统通过此函数来对游戏界面进行调整,如横竖屏自适应等。 + +此函数会被较少用到。 + +## 复写函数 + +样板的功能毕竟是写死的,有时候我们也需要修改样板的一些行为。 + +在V2.6以前,需要直接打开libs目录下的对应文件并进行修改。但是开libs下的文件就会出现各种问题: + +- 不容易记得自己修改过什么,而且如果改错了很麻烦 + - 例如,直接修改了某函数加了新功能,结果过段时间发现不需要,想删掉,但是这时候已经很难找到自己改过了什么了。 + - 或者,如果代码改错了,不断往上面打补丁,也只会使得libs越来越乱,最后连自己做过什么都不记得。 +- 不容易随着新样板接档进行迁移 +- 不方便能整理成新的插件在别的塔使用(总不能让别的塔也去修改libs吧) +- …… + +好消息是,从V2.6开始,我们再也不需要开文件了,而是可以直接在插件中对原始函数进行复写。 + +函数复写的好处如下: + +- 不会影响系统原有代码。 + - 即使写错了或不需要了,也只用把插件中的函数注释或删除即可,不会对原来的系统代码产生任何影响。 +- 清晰明了。很容易方便知道自己修改过什么,尤其是可以和系统原有代码进行对比。 +- 方便整理成新的插件,给其他的塔使用。 + +一般而言,复写规则如下: + +**对xxx文件中的yyy函数进行复写,规则是`core.xxx.yyy = function (参数列表) { ... }`。** + +但是,对于`register`系列函数是无效的,例如直接复写`core.control._animationFrame_globalAnimate`函数是没有效果的。对于这种情况引入的函数,需要注册同名函数,可参见最下面的样例。 + +下面是几个例子,从简单到复杂。 + +### 重写怪物手册的背景图绘制,使用winskin而不是默认的黑色 + +直接重写怪物手册的背景图绘制,使用`core.drawBackground`来用winskin绘制一个背景图。 + +```js +// 重写ui.js中的_drawBook_drawBackground函数 +core.ui._drawBook_drawBackground = function () { + // core.__PIXELS__为定义的一个宏,对于13x13的值是416,对于15x15的值是480 + core.drawBackground(0, 0, core.__PIXELS__, core.__PIXELS__); +} +``` + +### 重写点击楼传事件 + +重写点击楼传事件,使得点击楼传按钮时能使用一个道具(比如item:fly)。 + +```js +// 重写events.js的useFly函数,即点击楼传按钮时的事件 +core.events.useFly = function (fromUserAction) { + if (core.isMoving()) { + core.drawTip("请先停止勇士行动"); + return; + } + if (core.status.lockControl || core.status.event.id != null) return; + + if (core.canUseItem('fly')) core.useItem('fly'); + else core.drawTip("当前无法使用"+core.material.items.fly.name); +} +``` + +其他的几个按钮,如快捷商店`openQuickShop`,虚拟键盘`openKeyBoard`的重写也几乎完全一样。 + +### 关门时播放一个动画 + +关门是在`events.js`中的`closeDoor`函数,因此需要对其进行重写。 + +然而,我们只需要在这个函数执行之前插一句播放动画,所以并不需要重写整个函数,而是直接插入一行就行。 + +```js +var closeDoor = core.events.closeDoor; // 先把原始函数用一个变量记录下来 +core.events.closeDoor = function (x, y, id, callback) { + core.drawAnimate('closeDoor', x, y); // 播放 closeDoor 这个动画 + return closeDoor(x, y, id, callback); // 直接调用原始函数 +} +``` + +### 每次跳跃角色前播放一个音效 + +跳跃角色在`events.js`的`jumpHero`函数,因此需要对其进行重写。 + +由于只需要额外在函数执行前增加一句音效播放,所以直接插入一行即可。 + +但是需要注意的是,`jumpHero`中使用了`this._jumpHero_doJump()`,因此使用函数时需要用`call`或者`apply`来告知this是什么。 + +```js +var jumpHero = core.events.jumpHero; // 先把原始函数用一个变量记录下来 +core.events.jumpHero = function (ex, ey, time, callback) { + core.playSound("jump.mp3"); // 播放一个音效 + return jumpHero.call(core.events, ex, ey, time, callback); // 需要使用`call`来告知this是core.events +} +``` + +详见[call和apply的用法](https://www.jianshu.com/p/80ea0d1c04f8)。 + +### 复写全局动画绘制函数 + +全局动画绘制在`control.js`的`_animationFrame_globalAnimate`函数。 + +注意到此函数是由`registerAnimationFrame`注册的,因此直接复写是无效的。 + +其在control.js的注册的定义如下: + +```js +// 注册全局动画函数 +this.registerAnimationFrame("globalAnimate", true, this._animationFrame_globalAnimate); +``` + +因此,可以在插件中自行注册一个**同名**的函数来覆盖原始的内容。 + +```js +// 插件中复写全局动画绘制函数 +this.myGlobalAnimate = function (timestamp) { + // ...... 实际复写的函数内容 +} + +// 注册同名(globalAnimate)函数来覆盖系统原始内容 +core.registerAnimationFrame("globalAnimate", true, "myGlobalAnimate"); +``` + + +========================================================================================== + +[继续阅读下一章:修改编辑器](editor) + + diff --git a/_docs/search.min.js b/_docs/search.min.js new file mode 100644 index 0000000..9fd1931 --- /dev/null +++ b/_docs/search.min.js @@ -0,0 +1 @@ +!function(){"use strict";function e(e){var n={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return String(e).replace(/[&<>"'\/]/g,function(e){return n[e]})}function n(e){var n=[];return h.dom.findAll("a:not([data-nosearch])").map(function(t){var o=t.href,i=t.getAttribute("href"),r=e.parse(o).path;r&&-1===n.indexOf(r)&&!Docsify.util.isAbsolutePath(i)&&n.push(r)}),n}function t(e){localStorage.setItem("docsify.search.expires",Date.now()+e),localStorage.setItem("docsify.search.index",JSON.stringify(g))}function o(e,n,t,o){void 0===n&&(n="");var i,r=window.marked.lexer(n),a=window.Docsify.slugify,s={};return r.forEach(function(n){if("heading"===n.type&&n.depth<=o)i=t.toURL(e,{id:a(n.text)}),s[i]={slug:i,title:n.text,body:""};else{if(!i)return;s[i]?s[i].body?s[i].body+="\n"+(n.text||""):s[i].body=n.text:s[i]={slug:i,title:"",body:""}}}),a.clear(),s}function i(n){var t=[],o=[];Object.keys(g).forEach(function(e){o=o.concat(Object.keys(g[e]).map(function(n){return g[e][n]}))}),n=n.trim();var i=n.split(/[\s\-\,\\\/]+/);1!==i.length&&(i=[].concat(n,i));for(var r=0;rl.length&&(d=l.length);var p="..."+e(l).substring(f,d).replace(o,''+n+"")+"...";s+=p}}),a)){var d={title:e(c),content:s,url:f};t.push(d)}}(r);return t}function r(e,i){h=Docsify;var r="auto"===e.paths,a=localStorage.getItem("docsify.search.expires")
      ',o=Docsify.dom.create("div",t),i=Docsify.dom.find("aside");Docsify.dom.toggleClass(o,"search"),Docsify.dom.before(i,o)}function c(e){var n=Docsify.dom.find("div.search"),t=Docsify.dom.find(n,".results-panel");if(!e)return t.classList.remove("show"),void(t.innerHTML="");var o=i(e),r="";o.forEach(function(e){r+='
      \n \n

      '+e.title+"

      \n

      "+e.content+"

      \n
      \n
      "}),t.classList.add("show"),t.innerHTML=r||'

      '+y+"

      "}function l(){var e,n=Docsify.dom.find("div.search"),t=Docsify.dom.find(n,"input");Docsify.dom.on(n,"click",function(e){return"A"!==e.target.tagName&&e.stopPropagation()}),Docsify.dom.on(t,"input",function(n){clearTimeout(e),e=setTimeout(function(e){return c(n.target.value.trim())},100)})}function f(e,n){var t=Docsify.dom.getNode('.search input[type="search"]');if(t)if("string"==typeof e)t.placeholder=e;else{var o=Object.keys(e).filter(function(e){return n.indexOf(e)>-1})[0];t.placeholder=e[o]}}function d(e,n){if("string"==typeof e)y=e;else{var t=Object.keys(e).filter(function(e){return n.indexOf(e)>-1})[0];y=e[t]}}function p(e,n){var t=n.router.parse().query.s;a(),s(e,t),l(),t&&setTimeout(function(e){return c(t)},500)}function u(e,n){f(e.placeholder,n.route.path),d(e.noData,n.route.path)}var h,g={},y="",m={placeholder:"Type to search",noData:"No Results!",paths:"auto",depth:2,maxAge:864e5},v=function(e,n){var t=Docsify.util,o=n.config.search||m;Array.isArray(o)?m.paths=o:"object"==typeof o&&(m.paths=Array.isArray(o.paths)?o.paths:"auto",m.maxAge=t.isPrimitive(o.maxAge)?o.maxAge:m.maxAge,m.placeholder=o.placeholder||m.placeholder,m.noData=o.noData||m.noData,m.depth=o.depth||m.depth);var i="auto"===m.paths;e.mounted(function(e){p(m,n),!i&&r(m,n)}),e.doneEach(function(e){u(m,n),i&&r(m,n)})};$docsify.plugins=[].concat(v,$docsify.plugins)}(); \ No newline at end of file diff --git a/_docs/start.md b/_docs/start.md new file mode 100644 index 0000000..48a0f59 --- /dev/null +++ b/_docs/start.md @@ -0,0 +1,129 @@ +# 快速上手 + +?> 在这一节中,将详细介绍做一部塔的流程。现在,让我们来做一部单层塔! + +* [新版视频教程](https://www.bilibili.com/video/BV1SB4y1p7bg?share_source=copy_web) +* [脚本教程](https://www.bilibili.com/video/BV1uL411J7yZ?share_source=copy_web) + +## 前置需求 +你需要满足如下条件才能进行制作:[样板下载地址](https://github.com/ckcz123/mota-js/releases)。 +1. 操作系统: + - Win8或更高版本,Win7则需要安装 .Net Framework + 4.0(能打开根目录的“启动服务.exe”即可) + - 其他电脑则需安装[python + 3.9.6](https://www.python.org/downloads/)或更高版本,能运行根目录的server.py即可(Windows也可以这样做)。 + - 安卓手机需要安装[HTML5安卓造塔器](http://h5mota.com/games/_client/H5motaMaker.apk),推荐搭配ES文件浏览器。 +2. [谷歌chrome浏览器](http://google.cn/chrome)或win10自带的新版Edge浏览器:其他浏览器可能导致本地服务闪退。 +3. (强烈推荐)[VScode](http://code.visualstudio.com/download):最适合HTML5项目的文本编辑器,能进行跨文件的正则搜索和替换,也能完整发挥根目录的runtime.d.ts文件的作用。 + +只要满足了上述条件,你就可以开始做自己的塔啦! + +## 启动HTTP服务 + +与编辑器闭源的RPG Maker MV/MZ不同,本样板对文件的绝大部分修改是通过网页编辑器经由本地HTTP服务完成的,这样做也有助于编辑器跨平台并最大限度地复用运行时代码,还可以让玩家在在线游戏时查看游戏工程。 + +在根目录下有一个“启动服务.exe”,运行之。(非Windows电脑则需使用命令行运行`server.py`,安卓手机则使用造塔器) + +!> 使用python启动服务的话,整个塔的绝对路径必须是全英文! + +![image](img/server.jpg) + +* 启动游戏:打开[http://127.0.0.1:1055/index.html](http://127.0.0.1:1055/index.html),同时开启多个启动服务则1056、1057顺延(下同)。你能在里面看到现在游戏的效果。 +* 启动编辑器:打开[http://127.0.0.1:1055/editor.html](http://127.0.0.1:1055/editor.html)(竖屏则为editor-mobile.html),这是您整个制作流程的核心页面 + +以下为Windows专用的一些辅助工具(位于“辅助工具”文件夹),由C#编写: + +* 便捷PS工具:能方便地替换和新增32×32、32×48素材。V2.8.1对该工具进行了大幅强化,您甚至可以指定32×16的格子尺寸,作为前述两种尺寸的过渡操作。 +* 地图生成器:识别RPG Maker魔塔的地图截图,生成HTML5魔塔的地图数据。 +* 怪物数据导出:从RPG Maker XP 1.03游戏导出怪物数据,用于HTML5魔塔或使用Excel查看。 +* RM动画导出:从RPG Maker XP 1.03游戏导出动画,用于HTML5魔塔。 +* JS代码压缩:对js代码(限es5)和音像素材(背景音乐除外)进行压缩,从而减小文件体积,加快在线游戏的加载。一般无需手动运行,发布后我们的服务器会处理的。 +* 动画编辑器:编辑`project\animates`文件夹中的动画文件,或利用图片制作全新的动画。 +* 伤害和临界值计算器、帮助文档:如题,后者会打开本文档。 + +!> **整个造塔过程中,启动服务必须全程处于开启状态!切不可手滑关闭,否则做的都是无用功!** + +![image](img/V2_8server.jpg) + +V2.7.x中,“地图生成器”因为图片文件被拆分到两个文件夹的缘故而无法再使用,该问题已在V2.8中修复,同时该工具更名为“截图识别器”。 + +V2.8中,“额外素材合并”不再在本地进行而是改在作品发布时在服务器端用python实现,并把实现原理改为“将未用到的图块透明化后,全图裁剪到可能的最小尺寸”,以避免图块id错乱的问题。 + +## 编辑器页面的结构 + +![image](img/editor.jpg) + +如上图,编辑器页面的结构分为三大部分。左边叫**数据区**,中央叫**地图区**,右侧叫**素材区**,竖屏状态下同时只能显示其中一个,需要经常来回切换。 + +请尤其注意中央下方的下拉框,您可以随时按下Z、X、…、句号键(字母键第三行)让数据区在这些模式之间切换。更多键鼠快捷操作请按下H键查看,这里列出一部分: +1. **Alt+0~9、0~9:**给素材区的图块绑定数字快捷键,并使用。(您也可以用中央下方的“最近/最常使用图块”和置顶来代替) +2. **WASD、或单击/长按四个箭头按钮:**滚动大地图,还可以单击“大地图”按钮观看全景。 +3. **Ctrl+W/A/S/Z/X/C/V/Y:**关闭、全选、保存、撤销、剪切、复制、粘贴、重做绘图等操作。 +4. **PageUp/PageDown或滚轮:**切换楼层。 +5. **ESC、Delete:**取消选中并保存、删除选中点的图块和事件。(需要保存时“保存地图”按钮也会变色提示) +6. **地图上单击、双击、右击:**地图选点、选中该点的素材并自动定位到素材区、弹出菜单(您可以进行出生点、机关门、上下楼的快速绑定等操作) +7. **地图上左键或右键拖动:**交换两个点的图块和事件、框选一些点供Ctrl+X/C/V剪切复制。 +8. **素材区最右侧的tileset区域左键拖动:**框选一批素材,供在地图区单击批量绘制或左键拖动平铺。(右击或触屏点击则是手动输入所需的宽高) +9. **事件编辑器中Ctrl+S/Z/X/C/V/Y、右击、双击等:**执行相应操作,如双击可以进入多行编辑/绘制预览/弹窗选文件/地图选点,地图选点时右击可以选多个点。 + +## 快速上手 + +针对红海塔作者,这里给出一个极简版的造塔流程,您可以据此造出一座没有任何事件(包括但不限于难度分歧、老人、木牌、商人和商店等)的塔: +1. 编辑勇士的出生点和初始属性: + 1. 滚轮切换到主塔0层,右击地图任意位置,绑定出生点为此点(会有一个大大的白色S字母显示出来)。 + 2. 按下B键切换到“全塔属性”,填写`core.firstData.hero`中勇士的各项初始属性,以及一些全局的数值(如四种血瓶和三种宝石的效果、破甲反击净化的比例等,注意“唯一英文标识符”name一定要修改!)。 + 3. 在数据区使用滚轮向下翻(您可以随时折叠全塔属性的几大部分),按需编辑下面的大量勾选框(主要就是状态栏的那些显示项)。 +2. 从素材区选择各种各样的非NPC图块绘制在地图上,如门、怪物、道具、楼梯、路障、箭头、踩灯、箱子等。每当选中一个素材时,数据区就进入了“图块属性”模式,您可以去填写道具的一些说明、以及修改其他一些图块的可通行性等。注意滑冰(触发器为ski)要画在背景层。如果您需要制作机关门,请简单地将机关门和守卫(不支持阻击怪和炸弹)画在地图上,再右击机关门快速绑定即可。(看到机关门左下角出现橙色小方块、守卫们左下角出现黄色小方块即说明绑定成功) +3. 如果您需要制作多个楼层,只需按下Z键将数据区切换到“地图编辑”模式,然后“新建空白地图”即可,不同楼层之间简单地通过楼梯来往返,您可以将楼梯画在地图上再右击快速绑定即可。(看到楼梯左下角出现绿色小方块即说明绑定成功)各个楼层的属性可以通过按下V键将数据区切换到“楼层属性”模式来填写,如能否使用楼传、是否为地下层、画面色调、宝石血瓶倍率等。 +4. 从素材区选择您所使用的各种怪物,在数据区填写它们的各项属性,其中“特殊属性”是通过多选框来编辑的。 +5. 游戏胜利的触发:滚轮切换到样板1层,单击地图上的公主,按下Ctrl+C复制。滚轮切换回您的boss层,(记得给boss设置不可被炸哦)单击boss身后的任何一个空格子,按下Ctrl+V粘贴即可。这样玩家触碰到公主游戏就会胜利。 + +## 控制台调试 + +![image](img/console2.jpg) + +HTML5项目都是可以进行控制台调试的,下面以edge浏览器为例介绍其部分操作: + +首先按下F12键(部分键盘没有此键或需与Fn键一起按)或Ctrl+Shift+I,打开开发人员界面。 + +可以看到它分为“元素”、“控制台”、“调试程序”、“性能”等多个部分: +1. **元素:**您可以在这里对游戏和编辑器的各HTML和css元素进行查看和临时的修改,譬如您想观察游戏在竖屏的表现,只需将窗口拉到瘦高。 +2. **性能:**您可以在这里对游戏的任何一段脚本进行性能分析,观察其中各行的执行频率和耗时,从而确定优化的方向。 +3. **调试程序:**您可以在这里查看游戏的源码,包括project文件夹的functions.js和plugin.js(脚本编辑和插件编写)以及整个libs文件夹,并进行断点调试。 +4. **控制台:**最常使用的部分,当编辑器或游戏打不开、卡死、或者不按您的预想运作时您就需要查看这里的报错信息。这里也是各种API输入的地方,譬如上图中您可以看到全部的插件函数。 + +在控制台中,我们可以输入一些命令对游戏进行调试,常见的命令有: + +- `core.status.floorId` 获得当前层的floorId。 +- `core.status.thisMap` 获得当前地图信息。例如`core.status.thisMap.blocks`可以获得当前层所有图块。 +- `core.floors` 获得所有楼层的初始信息。例如`core.floors[core.status.floorId].events`可以获得当前层所有事件。 +- `core.status.hero` (或简写为hero)获得当前勇士状态信息。例如`core.status.hero.atk`就是当前勇士的攻击力数值。 +- `core.material.enemys` 获得所有怪物信息。例如`core.material.enemys.greenSlime`就是获得绿色史莱姆的属性数据。 +- `core.material.items` 获得所有道具的信息。例如`core.material.items.pickaxe`就是获得破墙镐的信息。 +- `core.debug()` 开启调试模式;此模式下可以按住Ctrl键进行穿墙。 +- `core.updateStatusBar()` 立刻更新状态栏、地图显伤并触发自动事件。 +- `core.setStatus('atk', 1000)` 直接设置勇士的某项属性。本句等价于 `core.status.hero.atk = 1000`。 +- `core.getStatus('atk')` 返回勇士当前某项属性数值。本句等价于 `core.status.hero.atk`。 +- `core.setItem('pickaxe', 10)` 直接设置勇士某个道具的个数。这里可以需要写道具的ID。 +- `core.getItem('pickaxe', 2)` 令勇士获得两个破墙镐。 +- `core.itemCount('pickaxe')` 返回勇士某个道具的个数。 +- `core.hasItem('pickaxe')` 返回勇士是否拥有某个道具。等价于`core.itemCount('pickaxe')>0`。 +- `core.getEquip(0)` 返回0号装备类型(武器)的当前装备的itemId,不存在则返回`null` +- `core.hasEquip('sword1')` 返回某个装备当前是否处于被装备状态 +- `core.setFlag('xxx', 1)` 设置某个flag/自定义变量的值,可以设为`null`表示删除。 +- `core.getFlag('xxx', 10)` 获得某个flag/自定义变量的值;如果该项不存在(未被定义),则返回第二个参数的值。 +- `core.hasFlag('xxx')` 返回是否存在某个变量且不为0。等价于`!!core.getFlag('xxx', 0)`。 +- `core.removeFlag('xxx')` 删除某个flag/自定义变量 +- `core.insertAction(list)` 执行一段自定义事件。比如 `core.insertAction(["剧情文本"])` 将执行一个剧情文本显示事件。 +- `core.changeFloor('MT2', 'downFloor')` 立刻执行楼层切换到MT2层的下楼点位置。 +- `core.changeFloor('MT5', null, {'x': 4, 'y': 7})` 立刻切换楼层到MT5层的(4,7)点。 +- `core.getBlock(3, 5, 'MT1')` 获得当前地图上某一个块的信息。第三个参数为floorId,可省略表示当前楼层。 +- `core.getBlockId(3, 5, 'MT1')` 获得当前地图上某一个点的图块ID。第三个参数为floorId,可省略表示当前楼层。 +- `core.resetMap()` 重置当前层地图。 +- …… + +更多关于控制台调试和脚本的信息可参见[脚本](script)和[API列表](api)。 + +========================================================================================== + +[继续阅读下一章:元件说明](element) diff --git a/_docs/ui-editor.md b/_docs/ui-editor.md new file mode 100644 index 0000000..d7deb6a --- /dev/null +++ b/_docs/ui-editor.md @@ -0,0 +1,864 @@ +# UI编辑器 + +## 基础操作 + +打开编辑界面后即可编辑,通过添加新操作可以向ui中添加新的内容。点击一个操作后可以选中该操作,被选中的操作将呈现黄色,可按住ctrl多选。此时点击鼠标右键,可以打开右键菜单,包括在上方插入、复制等操作。执行在上方或下方插入时,会在鼠标点击的操作上方或下方插入,执行复制或剪切时会复制或剪切所有被选中的操作,被剪切的操作将会呈现灰色。当ui编辑到一半时,可以点击保存按钮进行保存,当ui未保存时,保存按钮应当显示为黄色,点击保存后,如果保存成功,按钮会短暂变成绿色。当ui编辑完成后,可以点击预览按钮从头开始预览ui,确认符合预期后,可以点击编译按钮将操作编译为js代码,然后将该代码按照编译完成的注释操作,将代码放入插件中,在需要的地方插入即可。 + +## 注意事项 + +- 设置过渡不会立刻生效,会在下一个动画帧中生效,因此设置之后可能需要大概20~50ms的等待操作 +- 画布无法更改名称 +- 对于绘制成段文本,如果设置了打字机速度,那么两个该操作不能同时进行 +- 临时禁用只对预览有效,编译时被临时禁用的操作仍会被编译 +- 遇到问题或bug、建议等请在造塔群及时反馈 + +## 编译 + +该操作会将当前编辑器显示的所有操作编译为js代码。编译完成后,应当放到插件中一个形式为`this.xxx = function () { ui内容 }`的函数中,在需要显示该ui时,只需执行代码`core.xxx()` + +示例: + +```js +// 插件中 +this.myUi = function() { + // 请复制以下内容至插件中以使用 + // 使用案例: + /* + 插件中: + this.showMyUi = function () { + 函数内容 + } + 调用时: + core.showMyUi(); + */ + var _sprite_0 = new Sprite(100, 100, 100, 100, 200, 'game', '_sprite_0'); + _sprite_0.setCss('display: none;'); + + action_0_6(); + + function action_0_6() { + _sprite_0.setCss('display: block;'); + _sprite_0.move(100, 100); + _sprite_0.resize(100, 100); + _sprite_0.canvas.style.zIndex = '200'; + core.setAlpha(_sprite_0.context, 0.5); + core.fillEllipse(_sprite_0.context, 50, 50, 30, 50, 0, ''); + var ctx = _sprite_0.context; + ctx.moveTo(0, 20); + ctx.bezierCurveTo(10, 10, 50, 50, 50, 100); + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 5; + ctx.stoke(); + _sprite_0.context.globalCompositeOperation = 'difference'; + core.fillRect(_sprite_0.context, 0, 0, 100, 100, ''); + setTimeout(action_7_8, 1000); + } + function action_7_8() { + var res = _sprite_0.canvas.style.transition; + if (res !== '') res += ', '; + res += 'left 300ms ease-out 0ms'; + _sprite_0.canvas.style.transition = res; + setTimeout(action_9_9, 50); + } + function action_9_9() { + _sprite_0.resize(100, 100, true); + } +} + +// 需要显示ui时 +core.myUi(); +``` + +## 预览 + +预览分为两种,一种是编辑时的即时预览,这种预览会显示所有的画布,并无视等待和删除,确保你能够看到所有的绘制内容,如果你想要隐藏某个操作,可以打开该操作的详细信息,然后将临时禁用勾上,这样就能隐藏该操作了。 + +第二种是点击预览按钮时发生的,这种预览会尽可能地还原编译后的执行结果,让你能够完整地预览ui。 + +## 保存 + +显而易见,该操作可以保存现在正在编辑的ui。注意,ui会保存在样板根目录下的_ui文件夹,以base64的形式进行保存。当你编辑了ui时,保存按钮会呈现黄色,说明此时你还没有保存,此时切出编辑页面会弹出提示。点击保存后,会出现短暂的保存中字样(保存快的话可能看不清),如果保存成功,会出现成功字样,并会将背景短暂地改成绿色 + +## 临时禁用 + +该操作可以让你能够禁用某个操作,让你在不删除操作的情况下看到删除之后的效果。该操作只对预览有效,这意味着编译仍会编译被禁用的操作。 + +## 编辑 + +### 创建画布 + +| 事件名称 | 创建画布 | +| --- | --- | +| 事件原形 | 创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深) | +| 功能描述 | 创建一个新的画布 | +| 输入参数 1 | 名称:画布的名称 | +| 输入参数 2 | 横坐标:画布的起点横坐标(左上角) | +| 输入参数 3 | 纵坐标:画布的起点纵坐标(左上角) | +| 输入参数 4 | 宽度:画布的宽度 | +| 输入参数 5 | 高度:画布的高度 | +| 输入参数 6 | 纵深:画布的显示优先级,数值越大优先级越高 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 创建画布 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +…… +``` + +### 等待 + +| 事件名称 | 等待 | +| --- | --- | +| 事件原形 | 等待(毫秒数) | +| 功能描述 | 等待一定时间后再执行后续操作 | +| 输入参数 1 | 毫秒数:等待的时间长度 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 等待 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +等待(毫秒数); +``` + +### 设置过渡 + +| 事件名称 | 设置过渡 | +| --- | --- | +| 事件原形 | 设置过渡(作用画布, 作用效果, 过渡时间, 过渡方式, 延迟) | +| 功能描述 | 指定画布前两个命令之间的渐变式变化 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 作用效果:可用于transition的style | +| 输入参数 3 | 过渡时间:变化的时间长度 | +| 输入参数 4 | 过渡方式:过渡方式,包括ease-out ease-in ease-in-out linear bezier-curve()等 | +| 输入参数 5 | 延迟:当指定属性变化后,多长时间后开始过渡 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +| 常见作用效果 | 说明 | +| --- | --- | +| left | x轴方向的位置 | +| top | y轴方向的位置 | +| opacity | 不透明度 | +| background-color | 背景色 | +| transform | 2D和3D变换 | +| filter | 滤镜 | +| border | 边框 | + +| 过渡方式 | 说明 | +| --- | --- | +| ease-out | 先快后慢 | +| ease-in | 先慢后快 | +| ease-in-out | 慢-快-慢 | +| linear | 线性变化(匀速) | +| bezier-curve() | 指定按照贝塞尔曲线的形式变化,1表示到达终点,0表示起点 | + +例: +```js +/* 这是UI编辑器中 设置过渡 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +设置过渡(作用画布, 作用效果, 过渡时间, 过渡方式, 延迟); +移动画布(名称, 横坐标, 纵坐标); +``` + +### 删除画布 + +| 事件名称 | 删除画布 | +| --- | ----------- | +| 事件原形 | 删除画布(作用画布) | +| 功能描述 | 删除一个已经存在的画布 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 画布已经存在 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 删除画布 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +删除画布(名称); +``` + +### 移动画布 + +| 事件名称 | 移动画布 | +| --- | --- | +| 事件原形 | 移动画布(作用画布, 横坐标, 纵坐标, 移动模式) | +| 功能描述 | 移动指定画布一定指定距离 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 横坐标:画布的起点横坐标(左上角) | +| 输入参数 3 | 纵坐标:画布的起点纵坐标(左上角) | +| 输入参数 4 | 移动模式:是,则为相对坐标模式,否则为绝对坐标模式 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +| 移动模式 | 说明 | +| --- | --- | +| 绝对坐标 | 横纵坐标为画布移动后左上角的坐标位置 | +| 相对坐标 | 横纵坐标为画布在横向与纵向上移动的距离值 | + +例: +```js +/* 这是UI编辑器中 移动画布 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +移动画布(名称, 横坐标, 纵坐标,false); +``` + +### 缩放画布 + +| 事件名称 | 缩放画布 | +| --- | --- | +| 事件原形 | 缩放画布(作用画布, 宽度, 高度, 修改样式) | +| 功能描述 | 调整画布至指定尺寸大小 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 宽度:画布新的宽度尺寸 | +| 输入参数 3 | 高度:画布新的高度尺寸 | +| 输入参数 4 | 修改样式:如果是,那么只会修改css的长宽,导致模糊,如果不是,那么会修改画布的长宽,清晰,但会清空已绘制内容 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 缩放画布 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +缩放画布(名称, 宽度, 高度, false); +``` + +### 旋转画布 + +| 事件名称 | 旋转画布 | +| --- | --- | +| 事件原形 | 旋转画布(作用画布, 中心横坐标, 中心纵坐标, 角度) | +| 功能描述 | 指定画布以指定的中心点旋转一定的角度 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 中心横坐标:旋转中心的横坐标 | +| 输入参数 3 | 中心纵坐标:旋转中心的纵坐标 | +| 输入参数 4 | 角度:旋转的角度,正数顺时针旋转,负数逆时针旋转 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 旋转画布 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +旋转画布(名称, 中心横坐标, 中心纵坐标, 角度); +``` + +### 擦除画布 + +| 事件名称 | 擦除画布 | +| --- | ----------- | +| 事件原形 | 擦除画布(作用画布, 横坐标, 纵坐标, 宽度, 高度) | +| 功能描述 | 擦除画布指定区域内的所有内容 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 横坐标:区域的起点横坐标(左上角) | +| 输入参数 3 | 纵坐标:区域的起点纵坐标(左上角) | +| 输入参数 4 | 宽度:区域的宽度 | +| 输入参数 5 | 高度:区域的高度 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +如果不填的话,默认擦除全部 + +例: +```js +/* 这是UI编辑器中 擦除画布 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +擦除画布(名称, 横坐标, 纵坐标, 宽度, 高度); +``` + +### 设置CSS效果 + +| 事件名称 | 设置CSS效果 | +| --- | ----------- | +| 事件原形 | 设置CSS效果(作用画布, CSS效果) | +| 功能描述 | 修改指定画布的样式 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | CSS效果:编辑框内输入自定义的CSS效果 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +| 常用CSS效果 | 说明 | +| --- | --- | +| opacity | 不透明度,一个浮点型数字,在0~1区间内 | +| background-color | 背景色,可以使用`#rgba #rrggbbaa #rgb #rrggbb rgb(r,g,b) rgba(r,g,b,a) 英文单词`共7种形式 | +| background-image | 背景图片,可使用`url(project/images/image.png)`的形式 | +| box-shadow | 元素阴影,形式为 `x偏移 y偏移 阴影大小 颜色, x偏移 y偏移 阴影大小 颜色` | +| transform | 元素的2D和3D转换,用法过多,请百度搜索或在mdn搜索 | +| filter | 元素滤镜,同上 | + +例: +```js +/* 这是UI编辑器中 设置CSS效果 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +设置CSS效果(名称, CSS效果); +``` + +### 设置混合方式 + +| 事件名称 | 设置混合方式 | +| --- | ----------- | +| 事件原形 | 设置混合方式(作用画布, 混合方式) | +| 功能描述 | 设置新绘制的内容与已绘制的内容间的叠加方式 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 混合方式:设置画布的混合方式 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +混合方式说明:包括`color color-burn color-dodge copy darken destination-atop destination-in destination-out destination-over difference exclusion hard-light hue lighten lighter luminosity multiply overlay saturation screen soft-light source-atop source-in source-out source-over xor`共26种,默认为source-over,其余效果请自行百度或尝试 + +例: +```js +/* 这是UI编辑器中 设置混合方式 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +设置混合方式(名称, 混合方式); +``` + +### 设置绘制滤镜 + +| 事件名称 | 设置绘制滤镜 | +| --- | ----------- | +| 事件原形 | 设置绘制滤镜(作用画布, 滤镜) | +| 功能描述 | 设置在绘制时的滤镜,不影响已绘制内容 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 滤镜:编辑框内输入自定义的滤镜 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +| 常见滤镜 | 说明 | +| --- | --- | +| blur | 高斯模糊,用法示例:blur(5px) | +| brightness | 亮度,100%为原始亮度,用法示例:brightness(120%) | +| contrast | 对比度,100%为原始对比度,用法示例:contrast(80%) | +| grayscale | 灰度,0%为原始灰度,设为100%将会变成黑白,用法示例:grayscale(75%) | +| opacity | 不透明度,100%为原始不透明度,设为0%将会完全透明,用法示例:opacity(80%) | + +滤镜填写示例:`blur(5px)brightness(120%)grayscale(50%)`,注意会完全覆盖之前的效果,也就是说之前的效果会全部失效 + +例: +```js +/* 这是UI编辑器中 设置绘制滤镜 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +设置绘制滤镜(名称, 滤镜); +``` + +### 设置阴影 + +| 事件名称 | 设置阴影 | +| --- | ----------- | +| 事件原形 | 设置阴影(作用画布, 阴影模糊, 阴影颜色, 阴影横坐标, 阴影纵坐标) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 阴影模糊:阴影的高斯模糊,与设置绘制滤镜的blur相同 | +| 输入参数 3 | 阴影颜色:与设置css效果中对background-color的描述相同 | +| 输入参数 4 | 阴影横坐标:阴影偏移绘制位置的横坐标 | +| 输入参数 5 | 阴影纵坐标:阴影偏移绘制位置的纵坐标 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 设置阴影 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +设置阴影(名称, 阴影模糊, 阴影颜色, 宽度, 高度); +``` + +### 设置文本属性 + +| 事件名称 | 设置文本属性 | +| --- | ----------- | +| 事件原形 | 设置文本属性(作用画布, 文本方向, 文本对齐, 文本基线) | +| 功能描述 | 设置文本方向、文本对齐、文本基线 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 文本方向:文字阅读的方向 | +| 输入参数 3 | 文本对齐:文本水平对齐的方向 | +| 输入参数 4 | 文本基线:文本竖直对齐的方向 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +| 文本方向 | 说明 | +| --- | --- | +| ltr | 从左往右 | +| rtl | 从右往左 | + +| 文本对齐 | 说明 | +| --- | --- | +| left | 左对齐 | +| center | 居中对齐 | +| right | 右对齐 | + +| 文本基线 | 说明 | +| --- | --- | +| bottom | 底部对齐 | +| middle | 居中对齐 | +| top | 顶部对齐 | + +例: +```js +/* 这是UI编辑器中 设置文本信息 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +设置文本属性(名称, 文本方向, 文本对齐, 文本基线); +``` + +### 设置不透明度 + +| 事件名称 | 设置不透明度 | +| --- | ----------- | +| 事件原形 | 设置不透明度(作用画布, 文本不透明度) | +| 功能描述 | 设置画布之后绘制的不透明度 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 不透明度:要设置到的不透明度 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +该操作对已绘制内容没有影响,如果想要对已绘制内容也产生影响,请使用设置css + +例: +```js +/* 这是UI编辑器中 设置不透明度 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +设置不透明度(名称, 不透明度); +``` + +### 保存画布属性 + +| 事件名称 | 保存画布属性 | +| --- | ----------- | +| 事件原形 | 保存画布属性(作用画布) | +| 功能描述 | 保存画布属性,注意之会保存之前设置的属性,如文本属性,不会保存绘制内容 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 保存画布属性 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +保存画布属性(名称); +``` + +### 回退画布属性 + +| 事件名称 | 回退画布属性 | +| --- | ----------- | +| 事件原形 | 回退画布属性(作用画布) | +| 功能描述 | 回退画布属性,注意只会回退画布的属性,如文本属性,不会回退绘制内容 | +| 输入参数 1 | 作用画布:画布的名称 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 回退画布属性 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +回退画布属性(名称); +``` + +### 绘制直线 + +| 事件名称 | 绘制直线 | +| --- | ----------- | +| 事件原形 | 绘制直线(作用画布, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 起点横坐标: | +| 输入参数 3 | 起点纵坐标: | +| 输入参数 4 | 终点横坐标: | +| 输入参数 4 | 终点纵坐标: | +| 输入参数 7 | 连线宽度: | +| 输入参数 8 | 连线颜色: | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制直线 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制直线(名称, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色); +``` + +### 绘制弧线 + +| 事件名称 | 绘制弧线 | +| --- | ----------- | +| 事件原形 | 绘制弧线(作用画布, 中心横坐标, 中心纵坐标, 半径, 起始弧度, 终止弧度, 是否为边框, 线条宽度, 颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 横坐标 | +| 输入参数 3 | 纵坐标 | +| 输入参数 4 | 半径 | +| 输入参数 4 | 起始弧度 | +| 输入参数 4 | 终止弧度 | +| 输入参数 6 | 是否为边框 | +| 输入参数 7 | 线条宽度 | +| 输入参数 8 | 颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制弧线 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制弧线(名称, 中心横坐标, 中心纵坐标, 半径, 起始弧度, 终止弧度, 是否为边框, 线条宽度, 颜色); +``` + +### 绘制圆 + +| 事件名称 | 绘制圆 | +| --- | ----------- | +| 事件原形 | 绘制圆(作用画布, 中心横坐标, 中心纵坐标, 半径, 是否为边框, 线条宽度, 颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 横坐标 | +| 输入参数 3 | 纵坐标 | +| 输入参数 4 | 半径 | +| 输入参数 6 | 是否为边框 | +| 输入参数 7 | 线条宽度 | +| 输入参数 8 | 颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制圆 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制圆(名称, 中心横坐标, 中心纵坐标, 半径, 是否为边框, 线条宽度, 颜色); +``` + +### 绘制矩形 + +| 事件名称 | 绘制矩形 | +| --- | ----------- | +| 事件原形 | 绘制矩形(作用画布, 横坐标, 纵坐标, 宽度, 高度, 是否为边框, 线条宽度, 颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 横坐标 | +| 输入参数 3 | 纵坐标 | +| 输入参数 4 | 宽度 | +| 输入参数 5 | 高度 | +| 输入参数 6 | 是否为边框 | +| 输入参数 7 | 线条宽度 | +| 输入参数 8 | 颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制矩形 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制矩形(名称, 横坐标, 纵坐标, 宽度, 高度, 是否为边框, 线条宽度, 颜色); +``` +### 绘制圆角矩形 + +| 事件名称 | 绘制圆角矩形 | +| --- | ----------- | +| 事件原形 | 绘制圆角矩形(作用画布, 横坐标, 纵坐标, 宽度, 高度, 圆角半径, 是否为边框, 线条宽度, 颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 横坐标 | +| 输入参数 3 | 纵坐标 | +| 输入参数 4 | 宽度 | +| 输入参数 5 | 高度 | +| 输入参数 5 | 圆角半径 | +| 输入参数 6 | 是否为边框 | +| 输入参数 7 | 线条宽度 | +| 输入参数 8 | 颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制圆角矩形 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制圆角矩形(名称, 横坐标, 纵坐标, 宽度, 高度, 圆角半径, 是否为边框, 线条宽度, 颜色); +``` + +### 绘制多边形 + +| 事件名称 | 绘制多边形 | +| --- | ----------- | +| 事件原形 | 绘制多边形(作用画布, 是否为边框, 线条宽度, 颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 是否为边框 | +| 输入参数 3 | 线条宽度 | +| 输入参数 4 | 颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制多边形 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制多边形(名称, 是否为边框, 线条宽度, 颜色); +``` + +### 绘制椭圆 + +| 事件名称 | 绘制椭圆 | +| --- | ----------- | +| 事件原形 | 绘制椭圆(作用画布, 中心横坐标, 中心纵坐标, 半长轴, 半短轴, 旋转角度, 是否为边框, 线条宽度, 颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 中心横坐标 | +| 输入参数 3 | 中心纵坐标 | +| 输入参数 4 | 半长轴 | +| 输入参数 5 | 半短轴 | +| 输入参数 6 | 旋转角度 | +| 输入参数 7 | 是否为边框 | +| 输入参数 8 | 线条宽度 | +| 输入参数 9 | 颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制椭圆 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制椭圆(名称, 中心横坐标, 中心纵坐标, 半长轴, 半短轴, 旋转角度, 是否为边框, 线条宽度, 颜色) +``` + +### 绘制箭头 + +| 事件名称 | 绘制箭头 | +| --- | ----------- | +| 事件原形 | 绘制箭头(作用画布, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 起点横坐标 | +| 输入参数 3 | 起点纵坐标 | +| 输入参数 4 | 终点横坐标 | +| 输入参数 5 | 终点纵坐标 | +| 输入参数 6 | 连线宽度 | +| 输入参数 7 | 连线颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制箭头 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制箭头(名称, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 连线宽度, 连线颜色); +``` + +### 绘制贝塞尔曲线 + +| 事件名称 | 绘制贝塞尔曲线 | +| --- | ----------- | +| 事件原形 | 绘制贝塞尔曲线(作用画布, 控制点1横坐标, 控制点1纵坐标, 控制点2横坐标, 控制点2纵坐标, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 线条宽度, 颜色) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 控制点1横坐标 | +| 输入参数 3 | 控制点1纵坐标 | +| 输入参数 4 | 控制点2横坐标 | +| 输入参数 5 | 控制点2纵坐标 | +| 输入参数 6 | 起点横坐标 | +| 输入参数 7 | 起点纵坐标 | +| 输入参数 8 | 终点横坐标 | +| 输入参数 9 | 终点纵坐标 | +| 输入参数 10 | 线条宽度 | +| 输入参数 11 | 颜色 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制贝塞尔曲线 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制贝塞尔曲线(名称, 控制点1横坐标, 控制点1纵坐标, 控制点2横坐标, 控制点2纵坐标, 起点横坐标, 起点纵坐标, 终点横坐标, 终点纵坐标, 线条宽度, 颜色); +``` + +### 绘制图片 + +| 事件名称 | 绘制图片 | +| --- | ----------- | +| 事件原形 | 绘制图片(作用画布, 图片, 裁切点横坐标, 裁切点纵坐标, 裁切宽度, 裁切高度, 绘制点横坐标, 绘制点纵坐标, 绘制宽度, 绘制高度) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 图片 | +| 输入参数 3 | 裁切点横坐标 | +| 输入参数 4 | 裁切点纵坐标 | +| 输入参数 5 | 裁切宽度 | +| 输入参数 6 | 裁切高度 | +| 输入参数 7 | 绘制点横坐标 | +| 输入参数 8 | 绘制点纵坐标 | +| 输入参数 9 | 绘制宽度 | +| 输入参数 10 | 绘制高度 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制图片 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制图片(名称, 图片, 裁切点横坐标, 裁切点纵坐标, 裁切宽度, 裁切高度, 绘制点横坐标, 绘制点纵坐标, 绘制宽度, 绘制高度); +``` + +### 绘制图标 + +| 事件名称 | 绘制图标 | +| --- | ----------- | +| 事件原形 | 绘制图标(作用画布, 图标id, 横坐标, 纵坐标, 帧数) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 图标id | +| 输入参数 3 | 横坐标 | +| 输入参数 4 | 纵坐标 | +| 输入参数 5 | 宽度 | +| 输入参数 6 | 高度 | +| 输入参数 7 | 帧数:绘制图标的第几帧 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制图标 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制图标(名称, 图标id, 横坐标, 纵坐标, 帧数); +``` + +### 绘制窗口皮肤 + +| 事件名称 | 绘制窗口皮肤 | +| --- | ----------- | +| 事件原形 | 绘制窗口皮肤(作用画布, 皮肤背景, 横坐标, 纵坐标, 宽度, 高度) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 皮肤背景:需为已注册的图片 | +| 输入参数 3 | 横坐标 | +| 输入参数 4 | 纵坐标 | +| 输入参数 5 | 宽度 | +| 输入参数 6 | 高度 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制窗口皮肤 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制窗口皮肤(名称, 皮肤背景, 横坐标, 纵坐标, 宽度, 高度); +``` + +### 绘制文本 + +| 事件名称 | 绘制文本 | +| --- | ----------- | +| 事件原形 | 绘制文本(作用画布, 文字, 横坐标, 纵坐标, 添加描边, 斜体, 字体, 字体大小, 字体粗细, 字体颜色, 描边颜色, 最大宽度) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 文本 | +| 输入参数 3 | 横坐标 | +| 输入参数 4 | 纵坐标 | +| 输入参数 5 | 添加描边 | +| 输入参数 6 | 斜体 | +| 输入参数 7 | 字体 | +| 输入参数 8 | 字体大小 | +| 输入参数 9 | 字体粗细 | +| 输入参数 10 | 字体颜色 | +| 输入参数 11 | 描边颜色 | +| 输入参数 12 | 最大宽度 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制文本 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制文本(名称, 文字, 横坐标, 纵坐标, 添加描边, 斜体, 字体, 字体大小, 字体粗细, 字体颜色, 描边颜色, 最大宽度); +``` + +### 绘制成段文本 + +| 事件名称 | 绘制成段文本 | +| --- | ----------- | +| 事件原形 | 绘制成段文本(作用画布, 文本, 横坐标, 纵坐标, 宽度, 颜色, 对齐, 字体大小, 行间距, 打字机时间间隔, 字体, 字符间距, 加粗, 斜体) | +| 功能描述 | | +| 输入参数 1 | 作用画布:画布的名称 | +| 输入参数 2 | 文本 | +| 输入参数 3 | 横坐标 | +| 输入参数 4 | 纵坐标 | +| 输入参数 5 | 宽度 | +| 输入参数 6 | 颜色 | +| 输入参数 7 | 对齐 | +| 输入参数 8 | 字体大小 | +| 输入参数 9 | 行间距 | +| 输入参数 10 | 打字机时间间隔 | +| 输入参数 11 | 字体 | +| 输入参数 12 | 字符间距 | +| 输入参数 13 | 加粗 | +| 输入参数 14 | 斜体 | +| 输出参数 | 无 | +| 返回值 | 无 | +| 先决条件 | 无 | +| 调用函数 | 无 | + +例: +```js +/* 这是UI编辑器中 绘制成段文本 的参考例程 */ +创建画布(名称, 横坐标, 纵坐标, 宽度, 高度, 纵深); +绘制成段文本(名称, 文本, 横坐标, 纵坐标, 宽度, 颜色, 对齐, 字体大小, 行间距, 打字机时间间隔, 字体, 字符间距, 加粗, 斜体); +``` + +========================================================================================== + +[继续阅读下一章:API](api) \ No newline at end of file diff --git a/_docs/vue.css b/_docs/vue.css new file mode 100644 index 0000000..a38b902 --- /dev/null +++ b/_docs/vue.css @@ -0,0 +1 @@ +@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}.emoji{height:19.2px;height:1.2rem;vertical-align:middle}.progress{background-color:#42b983;background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:5}.search .search-keyword,.search a:hover{color:#42b983;color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}.task-list-item{list-style-type:none}li input[type=checkbox]{margin:0 .2em .25em -1.6em;vertical-align:middle}.app-nav{left:0;margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:2}.app-nav p{margin:0}.app-nav>a{margin:0 16px;margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:#42b983;color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid #42b983;border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 16px;margin:0 1rem;padding:5px 0;position:relative}.app-nav li ul{background-color:#fff;border:1px solid #ddd;border-bottom-color:#ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:scroll;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:16px;line-height:1rem;margin:0;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.app-nav.no-badge{margin-right:25px}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner svg{color:#fff;fill:#42b983;fill:var(--theme-color,#42b983);height:80px;width:80px}.github-corner:hover .octo-arm{-webkit-animation:a .56s ease-in-out;animation:a .56s ease-in-out}main{display:block;position:relative;width:100vw;height:100%;z-index:0}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;top:0;bottom:0;left:0;position:absolute;transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out;width:300px;z-index:3}.sidebar>h1{margin:0 auto 16px;margin:0 auto 1rem;font-size:24px;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar ul{margin:0;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;bottom:0;left:0;position:absolute;text-align:center;transition:opacity .3s;width:30px;width:284px;z-index:4}.sidebar-toggle .sidebar-toggle-button:hover{opacity:.4}.sidebar-toggle span{background-color:#42b983;background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;top:0;right:0;bottom:0;left:300px;position:absolute;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:800px;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:16px;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;width:20px;height:20px;text-align:center;top:14px}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:16px;padding:1rem}body.close .sidebar{-webkit-transform:translateX(-300px);transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;overflow-x:hidden}.sidebar{left:-300px;transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:-webkit-transform .25s ease;transition:transform .25s ease;transition:transform .25s ease,-webkit-transform .25s ease}.app-nav,.github-corner{transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto}body.close .sidebar{-webkit-transform:translateX(300px);transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px}body.close .content{-webkit-transform:translateX(300px);transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner .octo-arm{-webkit-animation:a .56s ease-in-out;animation:a .56s ease-in-out}.github-corner:hover .octo-arm{-webkit-animation:none;animation:none}}@-webkit-keyframes a{0%,to{-webkit-transform:rotate(0);transform:rotate(0)}20%,60%{-webkit-transform:rotate(-25deg);transform:rotate(-25deg)}40%,80%{-webkit-transform:rotate(10deg);transform:rotate(10deg)}}@keyframes a{0%,to{-webkit-transform:rotate(0);transform:rotate(0)}20%,60%{-webkit-transform:rotate(-25deg);transform:rotate(-25deg)}40%,80%{-webkit-transform:rotate(10deg);transform:rotate(10deg)}}section.cover{-webkit-box-align:center;-ms-flex-align:center;align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;display:none}section.cover .cover-main{-webkit-box-flex:1;-ms-flex:1;flex:1;margin:-20px 16px 0;text-align:center;z-index:1}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:24px;line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:40px;font-size:2.5rem;font-weight:300;margin:10px 0 40px;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-7px;bottom:-.4375rem;font-size:16px;font-size:1rem;position:absolute}section.cover blockquote{font-size:24px;font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border-color:#42b983;border:1px solid var(--theme-color,#42b983);border-radius:2rem;box-sizing:border-box;color:#42b983;color:var(--theme-color,#42b983);display:inline-block;font-size:16.8px;font-size:1.05rem;letter-spacing:1.6px;letter-spacing:.1rem;margin-right:16px;margin-right:1rem;padding:.75em 32px;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:#42b983;background-color:var(--theme-color,#42b983);color:#fff;margin-right:0}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid #42b983;border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:#42b983;color:var(--theme-color,#42b983)}section.cover.show{display:-webkit-box;display:-ms-flexbox;display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;width:100%;height:100%}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0 6px 15px}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:#42b983;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:#42b983;color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:32px;font-size:2rem;margin:0 0 16px;margin:0 0 1rem}.markdown-section h2{font-size:28px;font-size:1.75rem;margin:45px 0 12.8px;margin:45px 0 .8rem}.markdown-section h3{font-size:24px;font-size:1.5rem;margin:40px 0 9.6px;margin:40px 0 .6rem}.markdown-section h4{font-size:20px;font-size:1.25rem}.markdown-section h5,.markdown-section h6{font-size:16px;font-size:1rem}.markdown-section h6{color:#777}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:25.6px;line-height:1.6rem;word-spacing:.8px;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:24px;padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid #42b983;border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code{border-radius:2px;color:#e96900;font-size:12.8px;font-size:.8rem;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section code,.markdown-section pre{background-color:#f8f8f8;font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section pre{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;line-height:24px;line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 22.4px;padding:0 1.4rem;position:relative;word-wrap:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:#42b983;color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:#42b983;color:var(--theme-color,#42b983)}.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;background-color:#f8f8f8;border-radius:2px;color:#525252;display:block;font-family:Roboto Mono,Monaco,courier,monospace;font-size:12.8px;font-size:.8rem;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;padding:2.2em 5px;white-space:inherit}.markdown-section code:after,.markdown-section code:before{letter-spacing:.8px;letter-spacing:.05rem}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:24px;min-height:1.5rem}pre:after{color:#ccc;content:attr(data-lang);font-size:9.6px;font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0} \ No newline at end of file