diff --git a/README.md b/README.md index dbe04285..40c46a5b 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,16 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! * [x] 怪物和NPC的行走图和朝向问题 * [x] 可以引入WindowSkin作为对话框的背景素材 -* [x] \r可以动态调整剧情文本的颜色 +* [x] 允许使用\t[标题,1.png]来绘制大头像图 +* [x] 对话框的宽度可以根据文本长度自动调整 +* [x] \r[red]可以动态调整剧情文本的颜色 * [x] 升级事件改用事件编辑器完成 * [x] 每层楼都增添该层的并行事件处理 * [x] 新增快捷键:N返回标题;P查看评论;O打开工程 * [x] 道具可以设置是否在回放时绘制道具栏或直接使用 -* [x] 追加素材一次可以追加多个 +* [x] 可以同时异步移动/跳跃勇士和多个NPC * [x] 可以同时异步移动两张或以上的图片了 +* [x] 追加素材一次可以追加多个 * [x] 菜单栏中新增虚拟键盘的弹出 * [x] 修复所有已知Bug;部分细节优化 diff --git a/_server/blockly/MotaAction.g4 b/_server/blockly/MotaAction.g4 index 69824c89..5cdf8049 100644 --- a/_server/blockly/MotaAction.g4 +++ b/_server/blockly/MotaAction.g4 @@ -1084,45 +1084,48 @@ return code; */; move_s - : '移动事件' 'x' PosString? ',' 'y' PosString? '动画时间' Int? '不消失' Bool BGNL? StepString Newline + : '移动事件' 'x' PosString? ',' 'y' PosString? '动画时间' Int? '不消失' Bool '不等待执行完毕' Bool BGNL? StepString Newline /* move_s tooltip : move: 让某个NPC/怪物移动,位置可不填代表当前事件 helpUrl : https://h5mota.com/games/template/docs/#/event?id=move%EF%BC%9A%E8%AE%A9%E6%9F%90%E4%B8%AAnpc%E6%80%AA%E7%89%A9%E7%A7%BB%E5%8A%A8 -default : ["","",500,false,"上右3下2左上左2"] +default : ["","",500,false,false,"上右3下2左上左2"] colour : this.eventColor var floorstr = ''; if (PosString_0 && PosString_1) { floorstr = ', "loc": ['+PosString_0+','+PosString_1+']'; } Int_0 = Int_0 ?(', "time": '+Int_0):''; -var code = '{"type": "move"'+floorstr+''+Int_0+', "steps": '+JSON.stringify(StepString_0)+', "keep": '+Bool_0+'},\n'; +Bool_0 = Bool_0?', "keep": true':''; +Bool_1 = Bool_1?', "async": true':''; +var code = '{"type": "move"'+floorstr+Int_0+Bool_0+Bool_1+', "steps": '+JSON.stringify(StepString_0)+'},\n'; return code; */; moveHero_s - : '移动勇士' '动画时间' Int? BGNL? StepString Newline + : '移动勇士' '动画时间' Int? '不等待执行完毕' Bool BGNL? StepString Newline /* moveHero_s tooltip : moveHero:移动勇士,用这种方式移动勇士的过程中将无视一切地形, 无视一切事件, 中毒状态也不会扣血 helpUrl : https://h5mota.com/games/template/docs/#/event?id=movehero%EF%BC%9A%E7%A7%BB%E5%8A%A8%E5%8B%87%E5%A3%AB -default : [500,"上右3下2左上左2"] +default : [500,false,"上右3下2左上左2"] colour : this.dataColor Int_0 = Int_0 ?(', "time": '+Int_0):''; -var code = '{"type": "moveHero"'+Int_0+', "steps": '+JSON.stringify(StepString_0)+'},\n'; +Bool_0 = Bool_0?', "async": true':''; +var code = '{"type": "moveHero"'+Int_0+Bool_0+', "steps": '+JSON.stringify(StepString_0)+'},\n'; return code; */; jump_s - : '跳跃事件' '起始 x' PosString? ',' 'y' PosString? '终止 x' PosString? ',' 'y' PosString? '动画时间' Int? '不消失' Bool Newline + : '跳跃事件' '起始 x' PosString? ',' 'y' PosString? '终止 x' PosString? ',' 'y' PosString? '动画时间' Int? '不消失' Bool '不等待执行完毕' Bool Newline /* jump_s tooltip : jump: 让某个NPC/怪物跳跃 helpUrl : https://h5mota.com/games/template/docs/#/event?id=jump%EF%BC%9A%E8%AE%A9%E6%9F%90%E4%B8%AANPC%2F%E6%80%AA%E7%89%A9%E8%B7%B3%E8%B7%83 -default : ["","","","",500,true] +default : ["","","","",500,true,false] colour : this.eventColor var floorstr = ''; if (PosString_0 && PosString_1) { @@ -1132,25 +1135,28 @@ if (PosString_2 && PosString_3) { floorstr += ', "to": ['+PosString_2+','+PosString_3+']'; } Int_0 = Int_0 ?(', "time": '+Int_0):''; -var code = '{"type": "jump"'+floorstr+''+Int_0+', "keep": '+Bool_0+'},\n'; +Bool_0 = Bool_0?', "keep": true':''; +Bool_1 = Bool_1?', "async": true':''; +var code = '{"type": "jump"'+floorstr+''+Int_0+Bool_0+Bool_1+'},\n'; return code; */; jumpHero_s - : '跳跃勇士' 'x' PosString? ',' 'y' PosString? '动画时间' Int? Newline + : '跳跃勇士' 'x' PosString? ',' 'y' PosString? '动画时间' Int? '不等待执行完毕' Bool Newline /* jumpHero_s tooltip : jumpHero: 跳跃勇士 helpUrl : https://h5mota.com/games/template/docs/#/event?id=jumpHero%EF%BC%9A%E8%B7%B3%E8%B7%83%E5%8B%87%E5%A3%AB -default : ["","",500] +default : ["","",500,false] colour : this.dataColor var floorstr = ''; if (PosString_0 && PosString_1) { floorstr = ', "loc": ['+PosString_0+','+PosString_1+']'; } Int_0 = Int_0 ?(', "time": '+Int_0):''; -var code = '{"type": "jumpHero"'+floorstr+Int_0+'},\n'; +Bool_0 = Bool_0?', "async": true':''; +var code = '{"type": "jumpHero"'+floorstr+Int_0+Bool_0+'},\n'; return code; */; @@ -1910,22 +1916,22 @@ ActionParser.prototype.parseAction = function() { case "move": // 移动事件 data.loc=data.loc||['','']; this.next = MotaActionBlocks['move_s'].xmlText([ - data.loc[0],data.loc[1],data.time||0,data.keep,this.StepString(data.steps),this.next]); + data.loc[0],data.loc[1],data.time||0,data.keep||false,data.async||false,this.StepString(data.steps),this.next]); break; case "moveHero": this.next = MotaActionBlocks['moveHero_s'].xmlText([ - data.time||0,this.StepString(data.steps),this.next]); + data.time||0,data.async||false,this.StepString(data.steps),this.next]); break; case "jump": // 跳跃事件 data.from=data.from||['','']; data.to=data.to||['','']; this.next = MotaActionBlocks['jump_s'].xmlText([ - data.from[0],data.from[1],data.to[0],data.to[1],data.time||0,data.keep,this.next]); + data.from[0],data.from[1],data.to[0],data.to[1],data.time||0,data.keep||false,data.async||false,this.next]); break; case "jumpHero": // 跳跃勇士 data.loc=data.loc||['',''] this.next = MotaActionBlocks['jumpHero_s'].xmlText([ - data.loc[0],data.loc[1],data.time||0,this.next]); + data.loc[0],data.loc[1],data.time||0,data.async||false,this.next]); break; case "changeFloor": // 楼层转换 data.loc=data.loc||['',''] diff --git a/docs/element.md b/docs/element.md index 5e838743..fc633a07 100644 --- a/docs/element.md +++ b/docs/element.md @@ -305,7 +305,7 @@ floorId指定的是目标楼层的唯一标识符(ID)。 **从2.1.1开始,楼层属性中提供了`upFloor`和`downFloor`两项。如果设置此项(比如`"upFloor": [2,3]`),则写stair:upFloor或者楼传器的落点将用此点来替换楼梯位置(即类似于RM中的上箭头)。** -## 剧情文本控制与界面皮肤 +## 剧情文本控制与对话框效果 在写剧情文本时,可以: @@ -314,6 +314,8 @@ floorId指定的是目标楼层的唯一标识符(ID)。 - 使用`\r[...]`来动态修改局部文本的颜色,如`\r[red]`。 - 使用`${}`来计算一个表达式的值,如`${status:atk+status:def}`。 +从V2.5.2开始,也允许绘制一张头像图在对话框中,只要通过`\t[1.png]`或`\t[标题,1.png]`的写法。 + 详细信息请参见[剧情文本控制](event#text:显示一段文字(剧情))中的说明。 从V2.5.2开始,可以用一张WindowSkin图片作为对话框的背景皮肤。 @@ -324,6 +326,14 @@ floorId指定的是目标楼层的唯一标识符(ID)。 !> 关于对话框效果请注意,现在是采用WindowSkin的右下角两个32x32的图片作为对话框尖角进行绘制。因此请尽量使用群文件或网盘的常用素材中给出的WindowSkin素材(均已进行对话框适配)。如需使用来自第三方的WindowSkin素材,请自行注意对话框的尖角问题,或弃用`\b`效果。 +另外一点是,V2.5.2以后,对话框`\b`可以根据文字长度来自动控制文本框宽度,其基本控制原理如下: + +- 如果用户存在手动换行`\n`,则选取**最长的一段话**作为文本框宽度。 +- 如果用户不存在手动换行,则会将文本框宽度调整为**尽量刚好达到三行**的最佳宽度。 +- 文本框宽度存在上下界,最终宽度一定会控制在该范围内。 + +该自动调整仅对`\b`的对话框效果有效。非对话框仍然会绘制整个界面的宽度。 + ## 大地图 从V2.4开始,H5魔塔开始支持大地图。 diff --git a/docs/event.md b/docs/event.md index 82c1b2ee..468dd3ea 100644 --- a/docs/event.md +++ b/docs/event.md @@ -207,6 +207,8 @@ 对于hero和怪物,也可以不写名字代表使用默认值。 +从V2.5.2以后,新增了绘制大头像的功能。绘制大头像图的基本写法是`\t[1.png]`或者`\t[标题,1.png]`。 + ``` js "x,y": [ // 实际执行的事件列表 "一段普通文字", @@ -216,9 +218,13 @@ "\t[blackMagician]如果使用怪物的默认名称也可以简写怪物id", "\t[小妖精,fairy]这是一段小妖精说的话,使用仙子(fairy)的图标", "\t[你赢了]直接显示标题为【你赢了】", + "\t[1.png]绘制1.png这个头像图", + "\t[标题,1.png]同时绘制标题和1.png这个头像图" ] ``` +!> 大头像的头像图需要在全塔属性中注册,且必须是png格式,不可以用jpg或者其他格式,请自行转换。 + 除此以外,我们还能实现“对话框效果”,只要有`\b[...]`就可以。 - `\b[up]` 直接显示在当前点上方。同样把这里的up换成down则为下方。 @@ -990,7 +996,7 @@ level为天气的强度等级,在1-10之间。1级为最弱,10级为最强 {"type": "move", "time": 750, "loc": [x,y], "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),loc为位置可选,steps为移动数组 {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 - ], "keep": true }, // keep可选,如果为true则不消失,否则渐变消失 + ], "keep": true, "async":true }, // keep可选,如果为true则不消失,否则渐变消失;async可选,如果为true则异步执行。 ] ``` @@ -1026,7 +1032,9 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 } ``` -即,在移动的到达点指定一个初始禁用的相同NPC,然后move事件中指定immediateHide使立刻消失,并show该到达点坐标使其立刻显示(看起来就像没有消失),然后就可以触发目标点的事件了。 +即,在移动的到达点指定一个事件,然后move事件中指定"keep":true,然后就可以触发目标点的事件了。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 ### moveHero:移动勇士 @@ -1036,7 +1044,7 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "moveHero", "time": 750, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 + {"type": "moveHero", "time": 750, "async": true, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 ]}, @@ -1047,6 +1055,8 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 不过值得注意的是,用这种方式移动勇士的过程中将无视一切地形,无视一切事件,中毒状态也不会扣血。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### jump:让某个NPC/怪物跳跃 如果我们需要移动某个NPC或怪物,可以使用`{"type": "jump"}`。 @@ -1055,7 +1065,7 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "jump", "from": [3,6], "to": [2,1], "time": 750, "keep": true}, + {"type": "jump", "from": [3,6], "to": [2,1], "time": 750, "keep": true, "async": true}, ] ``` @@ -1069,6 +1079,8 @@ keep为一个可选项,同上代表该跳跃完毕后是否不消失。如果 如果指定了`"keep": true`,则相当于会在目标地点触发一个`setBlock`事件;如需能继续对话交互请在目标地点再写事件。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### jumpHero:跳跃勇士 如果我们需要跳跃勇士,可以使用`{"type": "jumpHero"}`。 @@ -1077,7 +1089,7 @@ keep为一个可选项,同上代表该跳跃完毕后是否不消失。如果 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "jump", "loc": [3,6], "time": 750}, + {"type": "jump", "loc": [3,6], "time": 750, "async": true}, ] ``` @@ -1085,6 +1097,8 @@ loc为目标坐标,可以忽略表示原地跳跃(请注意是原地跳跃 time选项为该跳跃所需要用到的时间。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### playBgm:播放背景音乐 使用playBgm可以播放一个背景音乐。 diff --git a/libs/control.js b/libs/control.js index b1df368a..7c4b0663 100644 --- a/libs/control.js +++ b/libs/control.js @@ -832,12 +832,8 @@ control.prototype.moveHero = function (direction, callback) { /////// 使用事件让勇士移动。这个函数将不会触发任何事件 ////// control.prototype.eventMoveHero = function(steps, time, callback) { - time = time || 100; - core.clearMap('ui'); - core.setAlpha('ui', 1.0); - // 要运行的轨迹:将steps展开 var moveSteps=[]; steps.forEach(function (e) { @@ -864,14 +860,11 @@ control.prototype.eventMoveHero = function(steps, time, callback) { 'right': {'x': 1, 'y': 0} }; - // core.status.replay.animate=true; - var animate=window.setInterval(function() { var x=core.getHeroLoc('x'), y=core.getHeroLoc('y'); if (moveSteps.length==0) { clearInterval(animate); core.drawHero(null, x, y); - // core.status.replay.animate=false; if (core.isset(callback)) callback(); } else { @@ -902,9 +895,6 @@ control.prototype.jumpHero = function (ex, ey, time, callback) { if (!core.isset(ey)) ey=sy; time = time || 500; - core.clearMap('ui'); - core.setAlpha('ui', 1.0); - // core.status.replay.animate=true; core.playSound('jump.mp3'); @@ -954,7 +944,6 @@ control.prototype.jumpHero = function (ex, ey, time, callback) { core.setHeroLoc('x', ex); core.setHeroLoc('y', ey); core.drawHero(); - // core.status.replay.animate=false; if (core.isset(callback)) callback(); } diff --git a/libs/events.js b/libs/events.js index f6dfcc5f..91931ea1 100644 --- a/libs/events.js +++ b/libs/events.js @@ -598,14 +598,26 @@ events.prototype.doAction = function() { x=core.calValue(data.loc[0]); y=core.calValue(data.loc[1]); } - core.moveBlock(x,y,data.steps,data.time,data.keep,function() { - core.events.doAction(); - }) + if (data.async) { + core.moveBlock(x,y,data.steps,data.time,data.keep); + this.doAction(); + } + else { + core.moveBlock(x,y,data.steps,data.time,data.keep,function() { + core.events.doAction(); + }) + } break; case "moveHero": - core.eventMoveHero(data.steps,data.time,function() { - core.events.doAction(); - }); + if (data.async) { + core.eventMoveHero(data.steps, data.time); + this.doAction(); + } + else { + core.eventMoveHero(data.steps,data.time,function() { + core.events.doAction(); + }); + } break; case "jump": // 跳跃事件 { @@ -618,9 +630,15 @@ events.prototype.doAction = function() { ex=core.calValue(data.to[0]); ey=core.calValue(data.to[1]); } - core.jumpBlock(sx,sy,ex,ey,data.time,data.keep,function() { - core.events.doAction(); - }); + if (data.async) { + core.jumpBlock(sx,sy,ex,ey,data.time,data.keep); + this.doAction(); + } + else { + core.jumpBlock(sx,sy,ex,ey,data.time,data.keep,function() { + core.events.doAction(); + }); + } break; } case "jumpHero": @@ -630,9 +648,15 @@ events.prototype.doAction = function() { ex=core.calValue(data.loc[0]); ey=core.calValue(data.loc[1]); } - core.jumpHero(ex,ey,data.time,function() { - core.events.doAction(); - }); + if (data.async) { + core.jumpHero(ex,ey,data.time); + this.doAction(); + } + else { + core.jumpHero(ex,ey,data.time,function() { + core.events.doAction(); + }); + } break; } case "changeFloor": // 楼层转换 diff --git a/libs/maps.js b/libs/maps.js index 193bec97..229d6ebd 100644 --- a/libs/maps.js +++ b/libs/maps.js @@ -738,8 +738,6 @@ maps.prototype.getBlockCls = function (x, y, floorId, showDisable) { maps.prototype.moveBlock = function(x,y,steps,time,keep,callback) { time = time || 500; - core.clearMap('route'); - var block = core.getBlock(x,y); if (block==null) {// 不存在 if (core.isset(callback)) callback(); @@ -747,14 +745,9 @@ maps.prototype.moveBlock = function(x,y,steps,time,keep,callback) { } var id = block.block.id; - // core.status.replay.animate=true; - // 需要删除该块 core.removeBlock(x,y); - core.clearMap('ui'); - core.setAlpha('ui', 1.0); - block=block.block; var image, bx, by, height = block.event.height || 32; @@ -786,8 +779,8 @@ maps.prototype.moveBlock = function(x,y,steps,time,keep,callback) { faceIds = block.event.faceIds||{}; } - var opacityVal = 1; - core.setOpacity('route', opacityVal); + var alpha = 1; + core.setAlpha('route', alpha); core.canvas.route.drawImage(image, bx * 32, by * height, 32, height, block.x * 32, block.y * 32 +32 - height, 32, height); // 要运行的轨迹:将steps展开 @@ -834,15 +827,11 @@ maps.prototype.moveBlock = function(x,y,steps,time,keep,callback) { // 已经移动完毕,消失 if (moveSteps.length==0) { - if (keep) opacityVal=0; - else opacityVal -= 0.06; - core.setOpacity('route', opacityVal); + if (keep) alpha=0; + else alpha -= 0.06; core.clearMap('route', nowX, nowY-height+32, 32, height); - core.canvas.route.drawImage(image, animateCurrent * 32, by * height, 32, height, nowX, nowY-height+32, 32, height); - if (opacityVal<=0) { + if (alpha<=0) { clearInterval(animate); - core.clearMap('route'); - core.setOpacity('route', 1); // 不消失 if (keep) { core.setBlock(id, nowX/32, nowY/32); @@ -851,6 +840,11 @@ maps.prototype.moveBlock = function(x,y,steps,time,keep,callback) { // core.status.replay.animate=false; if (core.isset(callback)) callback(); } + else { + core.setAlpha('route', alpha); + core.canvas.route.drawImage(image, animateCurrent * 32, by * height, 32, height, nowX, nowY-height+32, 32, height); + core.setAlpha('route', 1); + } } else { // 移动中 @@ -865,10 +859,10 @@ maps.prototype.moveBlock = function(x,y,steps,time,keep,callback) { } } + core.clearMap('route', nowX, nowY-height+32, 32, height); step++; nowX+=scan[direction].x*2; nowY+=scan[direction].y*2; - core.clearMap('route', nowX-32, nowY-32, 96, 96); // 绘制 core.canvas.route.drawImage(image, animateCurrent * 32, by * height, 32, height, nowX, nowY-height+32, 32, height); if (step==16) { @@ -883,7 +877,6 @@ maps.prototype.moveBlock = function(x,y,steps,time,keep,callback) { ////// 显示跳跃某块的动画,达到{"type":"jump"}的效果 ////// maps.prototype.jumpBlock = function(sx,sy,ex,ey,time,keep,callback) { time = time || 500; - core.clearMap('route'); var block = core.getBlock(sx,sy); if (block==null) { if (core.isset(callback)) callback(); @@ -891,12 +884,8 @@ maps.prototype.jumpBlock = function(sx,sy,ex,ey,time,keep,callback) { } var id = block.block.id; - // core.status.replay.animate=true; - // 需要删除该块 core.removeBlock(sx,sy); - core.clearMap('ui'); - core.setAlpha('ui', 1.0); block=block.block; var image, bx, by, height = block.event.height || 32; @@ -926,8 +915,8 @@ maps.prototype.jumpBlock = function(sx,sy,ex,ey,time,keep,callback) { by = core.material.icons[block.event.cls][block.event.id]; } - var opacityVal = 1; - core.setOpacity('route', opacityVal); + var alpha = 1; + core.setAlpha('route', alpha); core.canvas.route.drawImage(image, bx*32, by * height, 32, height, block.x * 32, block.y * 32 +32 - height, 32, height); core.playSound('jump.mp3'); @@ -977,12 +966,10 @@ maps.prototype.jumpBlock = function(sx,sy,ex,ey,time,keep,callback) { core.canvas.route.drawImage(image, animateCurrent * 32, by * height, 32, height, drawX(), drawY()-height+32, 32, height); } else { - if (keep) opacityVal=0; - else opacityVal -= 0.06; - core.setOpacity('route', opacityVal); + if (keep) alpha=0; + else alpha -= 0.06; core.clearMap('route', drawX(), drawY()-height+32, 32, height); - core.canvas.route.drawImage(image, animateCurrent * 32, by * height, 32, height, drawX(), drawY()-height+32, 32, height); - if (opacityVal<=0) { + if (alpha<=0) { clearInterval(animate); core.clearMap('route'); core.setOpacity('route', 1); @@ -990,9 +977,13 @@ maps.prototype.jumpBlock = function(sx,sy,ex,ey,time,keep,callback) { core.setBlock(id, ex, ey); core.showBlock(ex, ey); } - // core.status.replay.animate=false; if (core.isset(callback)) callback(); } + else { + core.setAlpha('route', alpha); + core.canvas.route.drawImage(image, animateCurrent * 32, by * height, 32, height, drawX(), drawY()-height+32, 32, height); + core.setAlpha('route', 1); + } } }, time / 16 / core.status.replay.speed); diff --git a/libs/ui.js b/libs/ui.js index 680e007a..e642d6b4 100644 --- a/libs/ui.js +++ b/libs/ui.js @@ -314,21 +314,29 @@ ui.prototype.getTitleAndIcon = function (content) { content=content.substring(index+1); var ss=str.split(","); if (ss.length==1) { - id=ss[0]; - if (id=='hero') name = core.status.hero.name; - else if (core.isset(core.material.enemys[id])) { - name = core.material.enemys[id].name; - getInfo(id); + if (/^[-\w.]+\.png$/.test(ss[0])) { + image = core.material.images.images[ss[0]]; } else { - name=id; - id='npc'; + id=ss[0]; + if (id=='hero') name = core.status.hero.name; + else if (core.isset(core.material.enemys[id])) { + name = core.material.enemys[id].name; + getInfo(id); + } + else { + name=id; + id='npc'; + } } } else { name=ss[0]; id = 'npc'; if (ss[1]=='hero') id = 'hero'; + else if (/^[-\w.]+\.png$/.test(ss[1])) { + image = core.material.images.images[ss[1]]; + } else getInfo(ss[1]); } } @@ -390,9 +398,32 @@ ui.prototype.drawWindowSkin = function(background,canvas,x,y,w,h,direction,px,py // 仿RM窗口皮肤 ↑ } -// 绘制纯色的背景框 -ui.prototype.drawPureBackground = function (background,canvas,borderColor,x,y,w,h,direction,px,py) { +// 计算有效文本框的宽度 +ui.prototype.calTextBoxWidth = function (canvas, content, min_width, max_width) { + // 无限长度自动换行 + var allLines = core.splitLines(canvas, content); + // 如果不存在手动换行,则二分自动换行 + if (allLines.length == 1) { + var prefer_lines = 3; + var start = Math.floor(min_width), end = Math.floor(max_width); + while (start < end) { + var mid = Math.floor((start+end)/2); + if (core.splitLines(canvas, content, mid).length < prefer_lines) + end = mid; + else + start = mid + 1; + } + return mid; + } + // 存在手动换行:以最长的为准 + else { + var w = 0; + allLines.forEach(function (t) { + w = Math.max(w, core.canvas[canvas].measureText(t).width); + }); + return core.clamp(w, min_width, max_width); + } } ////// 绘制一个对话框 ////// @@ -469,17 +500,36 @@ ui.prototype.drawTextBox = function(content, showAll) { core.status.boxAnimateObjs = []; core.clearMap('ui'); - var left=7, right=416-left, width = right-left; - var content_left = left + 25; - if (id=='hero' || core.isset(icon)) content_left=left+63; - - var validWidth = right-content_left - 10; var font = textfont + 'px Verdana'; if (textAttribute.bold) font = "bold "+font; var realContent = content.replace(/(\r|\\r)(\[.*?])?/g, ""); - var height = 20 + (textfont+5)*(core.splitLines("ui", realContent, validWidth, font).length+1) - + (id=='hero'?core.material.icons.hero.height-10:core.isset(name)?iconHeight-10:0); + var leftSpace = 25, rightSpace = 12; + if (core.isset(px) && core.isset(py)) leftSpace = 20; + if (id=='hero' || core.isset(icon)) leftSpace = 62; // 行走图:15+32+15 + else if (core.isset(image)) leftSpace = 90; // 大头像:10+70+10 + var left = 7, right = 416 - left, width = right - left, validWidth = width - leftSpace - rightSpace; + + // 对话框效果:改为动态计算 + if (core.isset(px) && core.isset(py)) { + var min_width = 220 - leftSpace, max_width = validWidth; + core.setFont('ui', font); + validWidth = this.calTextBoxWidth('ui', realContent, min_width, max_width); + width = validWidth + leftSpace + rightSpace; + // left必须在7~416-7-width区间内,以保证left>=7,right<=416-7 + left = core.clamp(32*px+16-width/2, 7, 416-7-width); + right = left + width; + } + + var content_left = left + leftSpace; + var height = 30 + (textfont+5)*core.splitLines("ui", realContent, validWidth, font).length; + if (core.isset(name)) height += titlefont + 5; + if (id == 'hero') + height = Math.max(height, core.material.icons.hero.height+50); + else if (core.isset(icon)) + height = Math.max(height, iconHeight+50); + else if (core.isset(image)) + height = Math.max(height, 90); var xoffset = 11, yoffset = 16; @@ -539,10 +589,10 @@ ui.prototype.drawTextBox = function(content, showAll) { // 名称 core.canvas.ui.textAlign = "left"; - var content_top = top + 35; + var content_top = top + 15 + textfont; if (core.isset(id)) { - content_top = top+57; + content_top += (titlefont + 5); core.setFillStyle('ui', titleColor); core.setStrokeStyle('ui', titleColor); @@ -551,17 +601,17 @@ ui.prototype.drawTextBox = function(content, showAll) { core.setAlpha('ui', alpha); core.strokeRect('ui', left + 15 - 1, top + 40 - 1, 34, heroHeight+2, null, 2); core.setAlpha('ui', 1); - core.fillText('ui', name, content_left, top + 30, null, 'bold '+titlefont+'px Verdana'); + core.fillText('ui', name, content_left, top + 8 + titlefont, null, 'bold '+titlefont+'px Verdana'); core.clearMap('ui', left + 15, top + 40, 32, heroHeight); core.fillRect('ui', left + 15, top + 40, 32, heroHeight, core.material.groundPattern); var heroIcon = core.material.icons.hero['down']; core.canvas.ui.drawImage(core.material.images.hero, heroIcon.stop * 32, heroIcon.loc * heroHeight, 32, heroHeight, left+15, top+40, 32, heroHeight); } else { - core.fillText('ui', name, content_left, top + 30, null, 'bold '+titlefont+'px Verdana'); + core.fillText('ui', name, content_left, top + 8 + titlefont, null, 'bold '+titlefont+'px Verdana'); if (core.isset(icon)) { core.setAlpha('ui', alpha); - core.strokeRect('ui', left + 15 - 1, top + 40 - 1, 34, iconHeight + 2, null, 2); + core.strokeRect('ui', left + 15 - 1, top + 40-1, 34, iconHeight + 2, null, 2); core.setAlpha('ui', 1); core.status.boxAnimateObjs = []; core.status.boxAnimateObjs.push({ @@ -570,11 +620,13 @@ ui.prototype.drawTextBox = function(content, showAll) { 'image': image, 'pos': icon*iconHeight }); - core.drawBoxAnimate(); } } } + if (core.isset(image) && !core.isset(icon)) { + core.canvas.ui.drawImage(image, 0, 0, image.width, image.height, left+10, top+10, 70, 70); + } var offsetx = content_left, offsety = content_top; core.setFont('ui', font); diff --git a/libs/utils.js b/libs/utils.js index 6b58e8d0..7d62e27a 100644 --- a/libs/utils.js +++ b/libs/utils.js @@ -79,7 +79,7 @@ utils.prototype.splitLines = function(canvas, text, maxLength, font) { else { var toAdd = text.substring(last, i+1); var width = core.canvas[canvas].measureText(toAdd).width; - if (width>maxLength) { + if (core.isset(maxLength) && width>maxLength) { contents.push(text.substring(last, i)); last=i; } diff --git a/project/floors/sample0.js b/project/floors/sample0.js index 51810f8a..6cb7a5ae 100644 --- a/project/floors/sample0.js +++ b/project/floors/sample0.js @@ -25,6 +25,7 @@ main.floors.sample0= [ 88, 89, 90, 91, 92, 93, 94, 2, 81, 82, 83, 84, 86] ], "firstArrive": [ + {"type": "setText", "background": "winskin.png"}, "\t[样板提示]首次到达某层可以触发 firstArrive 事件,该事件可类似于RMXP中的“自动执行脚本”。\n\n本事件支持一切的事件类型,常常用来触发对话,例如:", "\t[hero]\b[up,hero]我是谁?我从哪来?我又要到哪去?", "\t[仙子,fairy]你问我...?我也不知道啊...", diff --git a/更新说明.txt b/更新说明.txt index 7dc8a64e..82f7e95f 100644 --- a/更新说明.txt +++ b/更新说明.txt @@ -2,13 +2,16 @@ 怪物和NPC的行走图和朝向问题(详见文档) 可以引入WindowSkin作为对话框的背景素材 -\r可以动态调整剧情文本的颜色 +允许使用\t[标题,1.png]来绘制大头像图 +对话框的宽度可以根据文本长度自动调整 +\r[red]可以动态调整剧情文本的颜色 升级事件改用事件编辑器完成 每层楼都增添该层的并行事件处理 新增快捷键:N返回标题;P查看评论;O打开工程 道具可以设置是否在回放时绘制道具栏或直接使用 -追加素材一次可以追加多个 +可以同时异步移动/跳跃勇士和多个NPC 可以同时异步移动两张或以上的图片了 +追加素材一次可以追加多个 菜单栏中新增虚拟键盘的弹出 修复所有已知Bug;部分细节优化