diff --git a/README.md b/README.md index 03a25ad8..25045c7d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,17 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! ## 更新说明 +### 2017.12.20 +* [x] 新增:本地HTTP服务器。 +* [x] 新增:可视化地图编辑工具。 +* [x] 新增:便捷PS工具。 +* [x] 新增:对Autotile图块的支持。 +* [x] 新增:怪物支持多种属性;添加仇恨属性。 +* [x] 移除了不再支持的checkBlock,现在对于领域和夹击无需再手动指定可能的点了。 +* [x] 新增:单向箭头、感叹号(单次通行)的支持。 +* [x] 新增:更多的默认素材,现在对于大多数地图风格无需P图,直接替换即可。 +* [x] 部分细节优化,一些已知的Bug进行了修复。 + ### 2017.12.16 * [x] 新增:战斗过程显示,可以在设置中关闭 diff --git a/[start server.cmd b/[start server.cmd deleted file mode 100644 index 814d7527..00000000 --- a/[start server.cmd +++ /dev/null @@ -1 +0,0 @@ -python _server.py \ No newline at end of file diff --git a/_server.py b/_server.py deleted file mode 100644 index e2955bc1..00000000 --- a/_server.py +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- -import json -import socket -import threading -import os -import time - -def httpserver(port = 7945): - import http.server - http.server.test(http.server.SimpleHTTPRequestHandler,port=port) - -def voidfunc(*a,**k): - pass -sysecho=print -mecho=voidfunc - -homepage='homepage' -strtemplate='HTTP/1.0 302 Move temporarily\r\nContent-Length: 0\r\nLocation: {urlstr}\r\n\r\n' #{urlstr} - -def mainget(urlstr): - funcAfter=lambda:0 - if False and urlstr == '/': - sysecho(''.join([ - 'GET / ',addr[0],':',str(addr[1]) - ])) - return (200,homepage,funcAfter) - if True: - return (strtemplate.format(urlstr='//127.0.0.1:7945'+urlstr),'',funcAfter) - return (404,'404') - -def mainpost(urlstr,body): - funcAfter=lambda:0 - if urlstr == '/': - out='name not match' - try: - op=json.loads(body) - name=op['name'] - op['func'] - op['args'] - except Exception as e: - return (200,'error format') - if name=='readUTF8file' and op['func']=='open': - with open('./'+op['args'][0],encoding='utf-8') as fid: - out=fid.read() - if name=='writeUTF8file' and op['func']=='open': - with open('./'+op['args'][0],'w',encoding='utf-8') as fid: - out=str(fid.write(op['args'][1])) - return (200,out,funcAfter) - return (403,'no service this url') - -def mainparse(header,body): - funcAfter=lambda:0 - for _tmp in [1]: - if header[:3]=='GET': - urlstr=header.split(' ',2)[1] - mainre = mainget(urlstr) - if len(mainre)==2: - header,body=mainre - else: - header,body,funcAfter=mainre - break - if header[:4]=='POST': - urlstr=header.split(' ',2)[1] - mainre = mainpost(urlstr,body) - if len(mainre)==2: - header,body=mainre - else: - header,body,funcAfter=mainre - break - if header=='': - header,body= (403,'') - break - header,body= (403,'') - body=body.encode('utf-8') - if type(header)==int: - codeDict={200:'200 OK',302:'302 Move temporarily',403:'403 Forbidden',404:'404 Not Found'} - header=('HTTP/1.0 {statu}\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: '.format(statu=codeDict[header])+str(len(body))+'\r\nAccess-Control-Allow-Origin: *\r\n\r\n') - #\r\nAccess-Control-Allow-Origin: * null : to test in chrome - header=header.encode('utf-8') - return (header,body,funcAfter) - -def tcplink(sock, addr): - mecho('\n\nAccept new connection from %s:%s...' % addr) - tempbuffer = [''] - data='' - header='' - body='' - while True: - d = sock.recv(512) - if d: - d=d.decode('utf-8') - tempbuffer.append(d) - tempend=tempbuffer[-1][-4:]+d - if '\r\n\r\n' in tempend: - headend=True - data=''.join(tempbuffer) - header, body = data.split('\r\n\r\n', 1) - if header[:3]=='GET': - tempbuffer=[] - break - tempbuffer=[body] - a=int(header.split('Content-Length:',1)[1].split('\r\n',1)[0])-len(body.encode('utf-8'))#str.len not equal byte.len - while a>0: - tempbuffer.append(sock.recv(min(a,512)).decode('utf-8')) - a=a-min(a,512) - break - else: - break - mecho('recv end\n===') - body = ''.join(tempbuffer) - mecho(header) - mecho('---') - if len(body)>250: - mecho(body[:100]) - mecho('...\n') - mecho(body[-100:]) - else: - mecho(body) - if True: - header,body,funcAfter=mainparse(header,body) - mecho('===\nsend start\n') - sock.send(header) - sock.send(body) - mecho('\nsend end\n===') - sock.close() - mecho('Connection from %s:%s closed.' % addr) - funcAfter() - -if __name__ == '__main__': - out = threading.Thread(target=httpserver) - out.start() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(('127.0.0.1', 80)) - s.listen(500) - sysecho('Waiting for connection...') - os.popen('explorer http://127.0.0.1:7945/drawMapGUI.html') - while True: - sock, addr = s.accept() - t = threading.Thread(target=tcplink, args=(sock, addr)) - t.start() \ No newline at end of file diff --git a/docs/element.md b/docs/element.md index 228551ac..668c0c2d 100644 --- a/docs/element.md +++ b/docs/element.md @@ -18,6 +18,8 @@ 如需修改某个道具的效果,在不同区域宝石数据发生变化等问题,请参见[自定义道具效果](personalization#自定义道具效果)的说明。 +**有关轻按,在data.js的系统变量中有定义。如果`enableGentleClick`为true,则鼠标(触摸屏)通过双击勇士,键盘通过空格可达到轻按效果,即不向前移动而获得前方物品。** + ## 门 本塔支持6种门,黄蓝红绿铁花。前五种门需要有对应的钥匙打开,花门只能通过调用`openDoor`事件进行打开。 @@ -31,9 +33,41 @@ 本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。 如有自己的怪物素材需求请参见[自定义素材](personalization#自定义素材)的内容。 -怪物可以由特殊属性,每个怪物最多只能有一个特殊属性。怪物的特殊属性所对应的数字(special)在下面的`getSpecialText`中定义,请勿对已有的属性进行修改。 +怪物可以有特殊属性,每个怪物可以有多个自定义属性。 -![怪物属性](./img/getspecialtext.png) +怪物的特殊属性所对应的数字(special)在下面的`getSpecialText`中定义,请勿对已有的属性进行修改。 + +``` js +enemys.prototype.getSpecialText = function (enemyId) { + if (enemyId == undefined) return ""; + var special = this.enemys[enemyId].special; + var text = []; + if (this.hasSpecial(special, 1)) text.push("先攻"); + if (this.hasSpecial(special, 2)) text.push("魔攻"); + if (this.hasSpecial(special, 3)) text.push("坚固"); + if (this.hasSpecial(special, 4)) text.push("2连击"); + if (this.hasSpecial(special, 5)) text.push("3连击"); + if (this.hasSpecial(special, 6)) text.push("4连击"); + if (this.hasSpecial(special, 7)) text.push("破甲"); + if (this.hasSpecial(special, 8)) text.push("反击"); + if (this.hasSpecial(special, 9)) text.push("净化"); + if (this.hasSpecial(special, 10)) text.push("模仿"); + if (this.hasSpecial(special, 11)) text.push("吸血"); + if (this.hasSpecial(special, 12)) text.push("中毒"); + if (this.hasSpecial(special, 13)) text.push("衰弱"); + if (this.hasSpecial(special, 14)) text.push("诅咒"); + if (this.hasSpecial(special, 15)) text.push("领域"); + if (this.hasSpecial(special, 16)) text.push("夹击"); + if (this.hasSpecial(special, 17)) text.push("仇恨"); + return text.join(" "); +} +``` + +如果需要双属性,则采用100x+y的写法。例如 103 则视为同时拥有1和3的属性,即先攻且坚固。同理1314为衰弱诅咒双属性。 + +如果需要三属性,则采用10000x+100y+z的写法。例如71116视为同时拥有7,11和16的属性,即破甲、吸血、夹击。 + +四个乃至更多的属性以此类推。 怪物的伤害计算在下面的`calDamage`函数中,如有自己需求的伤害计算公式请修改该函数的代码。 @@ -46,6 +80,7 @@ ![怪物吸血](./img/blood.png) 中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。 + 衰弱怪让勇士衰弱后,攻防会暂时下降一定的数值(直到衰弱状态解除恢复);这个下降的数值同在`data.js`中的values定义。 ![debuff](./img/debuff.png) @@ -54,17 +89,11 @@ 领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 -![怪物属性](./img/domainenemy.png) +![怪物领域](./img/domainenemy.png) -出于游戏性能的考虑,我们不可能每走一步都对领域和夹击进行检查。因此我们需要在本楼层的 checkBlock 中指明哪些点可能会触发领域和夹击事件,在这些点才会对领域和夹击进行检查和处理。 +请注意如果吸血和领域同时存在,则value会冲突。**因此请勿将吸血和领域放置在同一个怪物身上。** -![检查夹击和领域](./img/checkblock.png) - -!> **请注意这里的`"x,y"`代表该点的横坐标为x,纵坐标为y;即从左到右第x列,从上到下的第y行(从0开始计算)。** - -本塔不支持阻击、激光、仇恨、自爆、退化等属性。 - -(这些属性基本都太恶心了,恶心到都不想加上去) +本塔暂不支持阻击、激光、自爆、退化等属性。 如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。 diff --git a/docs/event.md b/docs/event.md index a64a5d09..6630f0e6 100644 --- a/docs/event.md +++ b/docs/event.md @@ -392,8 +392,8 @@ revisit常常使用在一些商人之类的地方,当用户购买物品后不 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "openDoor", "loc": [3,6], "floorId": "MT1"}, // 打开MT1层的[3,6]位置的门 - {"type": "openDoor", "loc": [3,6]}, // 如果是本层则可省略floorId + {"type": "openDoor", "loc": [3,6], "floorId": "MT1"}, // 打开MT1层的[3,6]位置的门 + {"type": "openDoor", "loc": [3,6]}, // 如果是本层则可省略floorId ] ``` @@ -426,6 +426,8 @@ direction为可选的,指定的话将使勇士的朝向变成该方向 time为可选的,指定的话将作为楼层切换动画的时间。 +!> **changeFloor到达一个新的楼层,将不会执行firstArrive事件!如有需求请在到达点设置自定义事件,然后使用type: trigger立刻调用之。** + ### changePos: 当前位置切换/勇士转向 有时候我们不想要楼层切换的动画效果,而是直接让勇士从A点到B点。 @@ -461,7 +463,7 @@ time为可选的,指定的话将作为楼层切换动画的时间。 ``` color为需要更改画面色调的颜色。它是一个三元数组,分别指定目标颜色的R,G,B值。 -- 常见颜色: 纯黑[0,0,0],纯白[255,255,255],纯红[255,255,0],等等。 +- 常见颜色: 纯黑[0,0,0],纯白[255,255,255],纯红[255,0,0],等等。 如果color不指定则恢复原样。 @@ -475,7 +477,7 @@ time为可选的,如果指定,则会作为更改画面色调的时间。 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "move", "time": 750, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 + {"type": "move", "time": 750, "loc": [x,y], "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),loc为位置可选,steps为移动数组 {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 ], "immediateHide": true }, //immediateHide可选,制定为true则立刻消失,否则渐变消失 @@ -484,13 +486,15 @@ time为可选的,如果指定,则会作为更改画面色调的时间。 time选项必须指定,为每移动一步所需要用到的时间。 +loc为需要移动的事件位置。可以省略,如果省略则移动本事件。 + steps为一个数组,其每一项为一个 `{"direction" : xxx, "value": n}`,表示该步是向xxx方向移动n步。 如果只移动一步可以直接简单的写方向字符串(`up/left/down/right`)。 immediateHide为一个可选项,代表该事件移动完毕后是否立刻消失。如果该项指定了并为true,则移动完毕后直接消失,否则以动画效果消失。 -值得注意的是,当调用move事件时,实际上是使事件脱离了原始地点。为了避免冲突,规定:move事件会自动调用hide事件。 +值得注意的是,当调用move事件时,实际上是使事件脱离了原始地点。为了避免冲突,规定:move事件会自动调用该点的hide事件。 换句话说,当move事件被调用后,该点本身的事件将被禁用。 @@ -516,6 +520,25 @@ move完毕后移动的NPC/怪物一定会消失,只不过可以通过immediate 即,在移动的到达点指定一个初始禁用的相同NPC,然后move事件中指定immediateHide使立刻消失,并show该到达点坐标使其立刻显示(看起来就像没有消失),然后就可以触发目标点的事件了。 +### moveHero:移动勇士 + +如果我们需要移动勇士,可以使用`{"type": "moveHero"}`。 + +下面是该事件常见的写法: + +``` js +"x,y": [ // 实际执行的事件列表 + {"type": "moveHero", "time": 750, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 + {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 + "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 + ]}, +] +``` + +可以看到,和上面的move事件几乎完全相同,除了少了immediateHide选项。 + +不过值得注意的是,用这种方式移动勇士的过程中将无视一切地形,无视一切事件,中毒状态也不会扣血。 + ### playSound: 播放音效 使用playSound可以立刻播放一个音效。 @@ -600,7 +623,7 @@ choices是一个很麻烦的事件,它将弹出一个列表供用户进行选 其大致写法如下: ``` js -"x, y": [ // 实际执行的事件列表 +"x,y": [ // 实际执行的事件列表 {"type": "choices", "text": "...", // 提示文字 "choices": [ {"text": "选项1文字", "action": [ diff --git a/docs/img/mapgui.png b/docs/img/mapgui.png new file mode 100644 index 00000000..d66c73ef Binary files /dev/null and b/docs/img/mapgui.png differ diff --git a/docs/img/ps.png b/docs/img/ps.png new file mode 100644 index 00000000..9e0837e3 Binary files /dev/null and b/docs/img/ps.png differ diff --git a/docs/img/server.png b/docs/img/server.png new file mode 100644 index 00000000..878f6d47 Binary files /dev/null and b/docs/img/server.png differ diff --git a/docs/personalization.md b/docs/personalization.md index f388de04..f3572232 100644 --- a/docs/personalization.md +++ b/docs/personalization.md @@ -6,14 +6,95 @@ 所有素材的图片都在`images`目录下。 - `animates.png` 为所有动画效果。主要是星空熔岩,开门,毒网,传送门之类的效果。为四帧。 -- `enemys.png` 为所有怪物的图片。地图生成器中对应的数字,从上至下依次是会从201开始计算(即,绿色史莱姆为201,小蝙蝠为205,依次类推)。请注意,动画效果为两帧,一般是原始四帧中的1和3。(四帧中12相同,34相同,因此只取1和3即可) -- `heros.png`为勇士行走图。 +- `autotile.png` 为Autotile块。全塔只支持一种Autotile,其ID为20。 +- `enemys.png` 为所有怪物的图片。其对应的数字,从上至下依次是会从201开始计算(即,绿色史莱姆为201,小蝙蝠为205,依次类推)。请注意,动画效果为两帧,一般是原始四帧中的1和3。(四帧中12相同,34相同,因此只取1和3即可) +- `heros.png` 为勇士行走图。 - `items.png` 为所有道具的图标。 - `npcs.png` 为所有NPC的图标,也是两帧。 - `terrains.png` 为所有地形的图标。 系统会读取`icon.js`文件,并获取每个ID对应的图标所在的位置。 +### 使用预定义的素材 + +在images目录的“默认素材”下给定了若干预定义的自定义素材。包括野外(草地),星空,木板等等都已经被预先给定。 + +如果你需要某个素材已经存在,则可以直接将其覆盖images目录下的同名文件,就能看到效果。 + +### 使用便捷PS工具生成素材 + +如果我们有更多的素材要求,我们可以使用“便捷PS工具”进行处理。 + +![便捷PS工具](img/ps.png) + +我们可以打开有需求改变的素材,和我们需要被替换的素材,然后简单的Ctrl+C和Ctrl+V操作即可。 + +用这种方式,我们能极快地替换或素材,包括需要新增的怪物。 + +### 添加素材到游戏 + +在使用地图编辑器编辑的过程中,我们有可能会出现“该数字和ID未被定义”的错误提示。 + +这是因为,该素材没有被定义,无法被游戏所识别。 + +#### 新添加自定义地形(路面、墙壁等) + +如果你在terrains.png中新增了一行: + +1. 指定一个唯一的英文ID,不能和terrains中现有的重复。 +2. 进入icons.js,在terrains分类下进行添加(对应图标在图片上的位置,即index) +3. 指定一个数字,在maps.js的getBlock下类似进行添加。 +``` js +if (id == 13) tmp.event = {'cls': 'animates', 'id': 'weakNet', 'noPass': false, 'trigger': 'passNet'}; // 衰网 +if (id == 14) tmp.event = {'cls': 'animates', 'id': 'curseNet', 'noPass': false, 'trigger': 'passNet'}; // 咒网 +if (id == 15) tmp.event = {'cls': 'animates', 'id': 'water', 'noPass': true}; // 水 +// 可以在此处类似添加数字-ID对应关系,但不能和任何已有的数字重复 +// autotile: 20 +if (id == 20) tmp.event = {'cls': 'autotile', 'id': 'autotile', 'noPass': true}; // autotile +``` + +#### 新添加道具 + +如果你需要新增一个未被定义的道具: + +1. 指定一个唯一的英文ID,不能和items中现有的重复。 +2. 进入icons.js,在items分类下进行添加(对应图标在图片上的位置,即index) +3. 指定一个数字,在maps.js的getBlock下类似进行添加。 +4. 在items.js中仿照其他道具,来添加道具的信息。 + +``` js +if (id == 63) tmp.event = {'cls': 'items', 'id': 'moneyPocket'} // 金钱袋 +if (id == 64) tmp.event = {'cls': 'items', 'id': 'shoes'} // 绿鞋 +if (id == 65) tmp.event = {'cls': 'items', 'id': 'hammer'} // 圣锤 +// 可以在这里添加自己的数字-ID对应关系,但不能和任何已有的数字重复 +``` + +有关如何自行实现一个道具的效果,参见[自定义道具效果](#自定义道具效果)。 + +#### 新添加怪物 + +如果我们需要新添加怪物,请在enemys.png中新增一行,然后复制粘贴上四帧怪物图的**1和3帧**。 + +然后执行如下操作: + +1. 指定一个唯一的英文ID,不能和enemys中现有的重复。 +2. 进入icons.js,在enemys分类下进行添加(对应图标在图片上的位置,即index) +3. 在maps.js的getBlock下继续进行添加。请注意其ID为200开始的顺序,即如果新增一行为261,依次类推 +4. 在items.js中仿照其他道具,来添加道具的信息。 + +``` js +if (id == 258) tmp.event = {'cls': 'enemys', 'id': 'octopus'}; +if (id == 259) tmp.event = {'cls': 'enemys', 'id': 'fairy'}; +if (id == 260) tmp.event = {'cls': 'enemys', 'id': 'greenKnight'}; +// 在此依次添加,数字要求是递增的 +``` + +有关如何自行实现一个怪物的特殊属性或伤害计算公式,参见[怪物的特殊属性](#怪物的特殊属性)。 + +#### 新添加NPC + +类似同上,给NPC指定ID,在icons.js中指定ID-index关系,在maps.js中指定ID-数字的关系,即可。 + ### 地图生成器使用自定义素材 地图生成器可以将数字和图标一一对应,从数字生成图标或从图标生成数字。 @@ -24,82 +105,6 @@ ![地图含义](./img/mapmean.png) -### 使用自定义地形(路面、墙壁等) - -你需要自行P一张图,里面是你需要的地形素材。然后将其覆盖`terrains.png`文件,请注意所有元件的位置需要完全相同对应。 - -岩浆、星空、传送门、箭头、门的开启动画、暗墙的开启动画在`animates.png`中,可能也需要对应进行修改。 - -请打开`items.js`中,对比素材的位置。 - -另外需要注意的一点是,本样板不支持`Autotile`,换句话说野外地图(草地等)这种自然风可能不会太适合。 - -### 使用自定义道具图标 - -如果你觉得已有的道具图标不够,想自己添加图标也是可以的。 - -如果`items.png`中不存在你需要的图标,则可以自己P一张图,将你需要的图标覆盖到某个用不到的图标上。或者也可以接着后面向下拉伸。 - -所有道具必须是`32x32`像素。 - -P图完毕后,可以在地图生成器中加入对应的数字和图标的对应关系。 - -要在系统中启用你的图标,你需要自己指定一个道具的ID(不能和任何已有的重名),然后进行如下操作: -1. 在`items.js`中道具的定义列表中编辑,修改你的自定义道具的名称,类型,说明文字等。 - - 即捡即用类道具的cls为items,消耗类道具的cls为tools,永久类道具的cls为constants。 -2. 在`icon.js`中,找到items一栏,往里面添加你的图标位置。 -3. 在`maps.js`中,找到对应位置,往里面添加自己的数字和道具的一一对应关系。(该数字可任意指定,不能和已有的冲突),类似下面这样 - -``` js -if (id == 63) tmp.event = {'cls': 'items', 'id': 'moneyPocket'} // 金钱袋 -if (id == 64) tmp.event = {'cls': 'items', 'id': 'shoes'} // 绿鞋 -if (id == 65) tmp.event = {'cls': 'items', 'id': 'hammer'} // 圣锤 -// 可以在这里添加自己的数字-道具对应关系 -``` - -有关如何自行实现一个道具的效果,参见[自定义道具效果](#自定义道具效果)。 - -### 使用自定义怪物图标 - -如果你觉得已有的怪物图标不够,也可以自行向后添加你需要的怪物。 - -在`enemys.js`中,直接向后P图,并将你需要的怪物的两帧动画放到正确的位置(注意:普通四帧动画的1和3帧)。 - -P图完毕后,可以在地图生成器中加入对应的数字和图标的对应关系。 - -要在系统中启用你的图标,你需要自己指定一个怪物的ID(不能和任何已有的重名),然后进行如下操作: - -1. 在`enemys.js`中,向怪物的定义列表中,添加一个怪物。 -2. 在`icon.js`中,找到enemys一栏,往里面添加你的图标位置。 -3. 在`maps.js`中,找到对应位置,往里面添加自己的数字和怪物的一一对应关系。一般对于怪物而言,对应的数字就是`200+`位置。 - -有关如何自行实现一个怪物的特殊属性或伤害计算公式,参见[怪物的特殊属性](#怪物的特殊属性)。 - -### 使用自定义NPC图标 - -同上,你也可以自定义NPC图标。只需要将你NPC的两帧动画接到`npcs.js`中即可。 - -P图完毕后,可以在地图生成器中加入对应的数字和图标的对应关系。 - -要在系统中启用你的图标,你需要自己指定一个NPC的ID(不能和任何已有的重名),然后进行如下操作: - -1. 在`icon.js`中,找到npcs一栏,往里面添加你的图标位置。 -2. 在`maps.js`中,找到对应位置,往里面添加自己的数字和NPC的一一对应关系。 - -### 使用自定义勇士图标 - -我们同样可以使用自定义的勇士图标,比如小可绒之类。 - -直接拿一个勇士的行走图覆盖`hero.png`即可。 - -**支持任何行走大图,如`32x32`, `48x32`, `64x32`等等。** - -  - -通过上述这几种方式,我们可以修改素材图片(使用自定义素材),指定数字并放入地图生成器中,然后在系统中进行启用。 - -!> **请注意:除了勇士行走图外,其他所有素材强制要求必须是`32x32`的,不然可能会造成不可预料的后果。** - ## 自定义道具效果 本节中将继续介绍如何自己编辑一个道具的效果。 @@ -167,24 +172,26 @@ if (itemId === 'shield5') { ``` js enemys.prototype.getExtraDamage = function (monster) { var extra_damage = 0; - if (monster.special == 11) { // 吸血 + if (this.hasSpecial(monster.special, 11)) { // 吸血 // 吸血的比例 extra_damage = core.status.hero.hp * monster.value; if (core.hasFlag("shield5")) extra_damage = 0; // 如果存在神圣盾,则免疫吸血 extra_damage = parseInt(extra_damage); } - return extra_damage; -} +// ... 下略 ``` -3. 免疫领域、夹击效果:在`events.js`中,找到checkBlock函数,并编辑成如果有神圣盾标记,则直接返回。 +3. 免疫领域、夹击效果:在`core.js`中,找到updateCheckBlock函数,并编辑成如果有神圣盾标记,则直接返回。 ``` js -////// 检查领域、夹击事件 ////// -events.prototype.checkBlock = function (x,y) { - if (core.hasFlag("shield5")) return; // 如果拥有神圣盾立刻返回,免疫领域和夹击 - var damage = 0; - // 获得四个方向的怪物 - var directions = [[0,-1],[-1,0],[0,1],[1,0]]; // 上,左,下,右 - var enemys = [null,null,null,null]; +// 更新领域、显伤点 +core.prototype.updateCheckBlock = function() { + if (!core.isset(core.status.thisMap)) return; + if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap(); + core.status.checkBlock = []; + if (core.hasItem('shield5')) return; // 如拥有神圣盾则直接返回 + for (var x=0;x<13;x++) { + for (var y=0;y<13;y++) { + // 计算(x,y)点伤害 + var damage = 0; // ... 下略 ``` 4. 如果有更高的需求,例如想让吸血效果变成一半(如异空间),则还是在上面这些地方进行对应的修改即可。 @@ -199,15 +206,28 @@ events.prototype.checkBlock = function (x,y) { 因此无敌属性可以这样设置: -``` js -// 我们需要对无敌属性指定一个数字,比如18 +先给无敌属性指定一个数字,例如18,在getSpecialText中定义 +``` js +// ... 上略 + if (this.hasSpecial(special, 15)) text.push("领域"); + if (this.hasSpecial(special, 16)) text.push("夹击"); + if (this.hasSpecial(special, 17)) text.push("仇恨"); + if (this.hasSpecial(special, 18)) text.push("无敌"); // 添加无敌的显示 + return text.join(" "); +} + +``` + +然后修改calDamage,如果无敌属性且勇士没有拥有十字架,则立刻返回无穷大。 + +``` js enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special) { - if (mon_special==18 && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架 + if (this.hasSpecial(mon_special, 18) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架 return 999999999; // 返回无限大 // 魔攻 - if (mon_special == 2) hero_def = 0; + if (this.hasSpecial(mon_special, 2)) hero_def = 0; // ... 下略 ``` diff --git a/docs/start.md b/docs/start.md index 0e9f64f6..127fd819 100644 --- a/docs/start.md +++ b/docs/start.md @@ -6,14 +6,26 @@ 你需要有满足如下条件才能进行制作: -- Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“地图生成器.exe”即可) +- Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可) - 任一款现代浏览器。强烈推荐Chrome。 - 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如:WebStorm,VSCode,或者至少也要Sublime Text。 ([VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。) -- RPG Maker XP,任一个魔塔样板(推荐魔塔样板7630) 只要满足了上述条件,你就可以开始做自己的塔啦! +## 启动HTTP服务 + +在根目录下有一个“启动服务.exe”,运行之。 + +![启动服务](img/server.png) + +* “启动游戏”按钮将打开一个网页,你能在里面看到现在游戏的效果。 +* “地图编辑器”允许你以可视化的方式进行编辑地图。 +* “便捷PS工具”能让你很方便的对自定义素材进行添加。参见[自定义素材](personalization#自定义素材)。 +* “地图生成器”能让你从已有的截图(如RMXP项目)中立刻生成可被本样板识别的地图数据。 +* “JS代码压缩工具”能对JS代码进行压缩,从而减少IO请求数和文件大小。 +* “伤害和临界值计算器”是一个很便捷的小工具,能对怪物的伤害和临界值进行计算。 + ## 新建剧本 类似于RMXP,本塔每层楼都是一个“剧本”,剧本内主要定义了本层的地图和各种事件。主函数将读取每个剧本,并生成实际的地图供游戏使用。 @@ -30,43 +42,48 @@ ## 绘制地图 -遗憾的是,我们的样板是没有像RMXP那样有着很好的UI界面,供大家直接进行绘图可视化操作的。然而,我们仍然可以利用已有的RMXP和魔塔样板,绘制好地图,然后利用目录中的“地图生成器”来转成样板所识别的格式。 +有两种绘制地图的方式:从头绘制地图;从RMXP中导入已有的地图。 -首先,我们打开RMXP和魔塔样板,来到绘制地图页面。 +### 从头绘制地图 -![绘制地图](./img/rmxp1.png) +我们直接打开“地图编辑器”,可以看到一个可视化的UI界面。 -然后,任意绘制一张地图。 +![地图编辑器](img/mapgui.png) -在这里我们就以1层小塔的地图为例。你也可以任意绘制自己的地图。 +然后可以在上面任意进行绘制地图。 + +!> **如果地图的数字和ID未被定义,则会进行提示:数字和ID未被定义!此时可能需要手动在icons.js和maps.js中定义对应的数字和ID。请参见[自定义素材](personalization#自定义素材)。** + +绘制地图完毕后,点击"导出地图",即可在左边看到对应的JSON数组,并且已经复制到了剪切板。将其粘贴到剧本中的map位置即可。 + +![地图数组](./img/maparray.png) + +### 从RMXP导入已有的地图 + +如果我们想复刻一个现有的,已经被RMXP所制作的塔,也有很便捷的方式,那就是用到我们的“地图生成器”。 + +首先,我们打开RMXP和对应的项目,可以看到它的地图。 ![绘制地图](./img/rmxp2.png) -(我把原塔素材改成了经典样式,但是本质上是一样的。) - -请注意,我们无需编辑任何事件。只需要让地图“看起来长成这个样子”就行。 - -当绘制好需要的地图后,我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。 +我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。 ![绘制地图](./img/rmxp3.png) 截图时请注意:**只截取有效游戏空间内数据,并且有效空间内的范围必须是13x13。(如果地图小于13*13,请用星空或墙壁填充到13x13)。** -确认地图的图片文件已经复制到剪切板后,我们打开工具中的“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。 +确认地图的图片文件已经复制到剪切板后,我们打开“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。 ![生成地图](./img/map1.png) -如果有识别不一致的存在,即生成的地图和实际的地图不符,则需要在左边的输入框内实际手动修改,然后再点“图片生成”即可。有关每个数字对应的图块名称,请参见images目录下的`meaning.txt` +然后点击“复制地图”,即可将地图数据复制到剪切板。 + +!> **如果有识别不一致的存在,即生成的地图和实际的地图不符,我们可以在地图编辑器中粘贴,再可视化进行编辑。** !> **地图生成器默认只支持经典素材。如果有自定义素材需求(例如原版的1层小塔那种素材),请参见[自定义素材](personalization#自定义素材)。** !> **请确保截图范围刚好为13x13,并且保证每个位置的像素都是32x32。** -经过确认,生成的地图和原始地图保持一致后,点击“复制地图”,然后粘贴到刚刚剧本文件里的maps中。 - -![地图数组](./img/maparray.png) - -通过这种在RMXP中画图,截图复制,再用地图生成器识别的方式,我们成功将我们需要的地图变成了样板可识别的格式。 ## 录入数据 diff --git a/drawMapGUI.html b/drawMapGUI.html index c59246dd..475b4c78 100644 --- a/drawMapGUI.html +++ b/drawMapGUI.html @@ -4,67 +4,176 @@
- -

可以在console中通过printf(str)来改变这里的值

+
+ + 0 1 2 3 4 5 6 7 8 9 10 11 12 + +

{{ errormsg }}

+
+
+ + + +
@@ -97,16 +230,36 @@
- - - +
+
+

图块编号:{{ infos['idnum'] }}

+

图块ID:{{ infos['id'] }}

+

该图块无对应的数字或ID存在,请先前往icons.js和maps.js中进行定义!

+

图块所在素材:{{ infos['images'] }}

+

图块索引:{{ infos['y'] }}

+
+
+

{{ mapMsg }}

+
+
+ + - +
+ + + + +
+
@@ -114,35 +267,48 @@ + + @@ -299,10 +465,9 @@ return 0; } - - updateMap = function () { //clearGrass(); + // console.log(map) for (var xx = 0; xx <= fullX; xx++) { for (var yy = 0; yy <= fullY; yy++) { if (!isGrass(xx, yy)) continue; @@ -341,7 +506,12 @@ for (var yy = 0; yy <= fullY; yy++) { if (isGrass(xx, yy)) continue; var mapxy=map[m(xx,yy)]; - if (typeof(mapxy) == typeof(-1) || typeof(mapxy) == typeof([][0]))continue; + if (typeof(mapxy) == typeof(-1))continue; + else if(typeof(mapxy) == typeof([][0])) {//未定义块画红块 + cxt.fillStyle = 'red'; + cxt.fillRect(xx*32, yy*32, 32, 32); + continue; + } //context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height) cxt.clearRect(xx*32, yy*32, 32, 32); cxt.drawImage(core.material.images[mapxy.images], 0, mapxy.y*32, 32, 32, xx*32, yy*32, 32, 32); @@ -359,7 +529,7 @@ var prefix='',postfix=''; if (weak){prefix='';} if (typeof(str)==="undefined")str=''; - pout.innerHTML=prefix+String(str)+postfix; + printOut.innerHTML=prefix+String(str)+postfix; } drawInitData = function(){ @@ -444,6 +614,11 @@ }//用于鼠标移出canvas时的自动清除状态 ui.onmousedown = function (e) { + if(!selectBox.isSelected) { + tip.whichShow = 1; + return; + } + holdingPath = 1; mouseOutCheck = 2; setTimeout(clear1); @@ -457,6 +632,10 @@ } ui.onmousemove = function (e) { + if(!selectBox.isSelected) { + // tip.whichShow = 1; + return; + } if (holdingPath == 0) { return; } mouseOutCheck = 2; @@ -482,15 +661,19 @@ } ui.onmouseup = function (e) { + if(!selectBox.isSelected) { + tip.whichShow = 1; + return; + } holdingPath = 0; e.stopPropagation(); var loc = eToLoc(e); if (stepPostfix.length) { - console.log(stepPostfix); + // console.log(stepPostfix); for (var ii = 0; ii < stepPostfix.length; ii++) map[m(stepPostfix[ii].x, stepPostfix[ii].y)] = info; map[fullX + 1 + fullY * (fullX + 1)] = -2; - console.log(map); + // console.log(map); updateMap(); holdingPath = 0; stepPostfix = []; @@ -498,7 +681,7 @@ } } - info=ids[20];//autotile + // info=ids[indexs[20][0]];//autotile data.onmousedown = function (e) { e.stopPropagation(); var loc = { @@ -507,23 +690,26 @@ 'size': 32 }; pos = locToPos(loc); - console.log(pos); + // console.log(pos); for (var spriter in widthsX){ if(pos.x>=widthsX[spriter][1] && pos.xcore.material.images[pos.images].height)pos.y=~~(core.material.images[pos.images].height/32)-1; + pos.y=0; + }else if((pos.y+1)*32>core.material.images[pos.images].height) + pos.y=~~(core.material.images[pos.images].height/32)-1; + selectBox.isSelected = true; + // console.log(pos,core.material.images[pos.images].height) dataSelection.style.left=pos.x*32+'px'; dataSelection.style.top=pos.y*32+'px'; info={'images':pos.images,'y':pos.y}; for (var ii=0;ii'+filestr+'\n

已复制到剪贴板

'+'


'); - poutTmp.select(); - document.execCommand("Copy"); - } + + \ No newline at end of file diff --git a/libs/maps.js b/libs/maps.js index 27c2efc3..fde12e18 100644 --- a/libs/maps.js +++ b/libs/maps.js @@ -76,7 +76,6 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 12) tmp.event = {'cls': 'animates', 'id': 'poisonNet', 'noPass': false, 'trigger': 'passNet'}; // 毒网 if (id == 13) tmp.event = {'cls': 'animates', 'id': 'weakNet', 'noPass': false, 'trigger': 'passNet'}; // 衰网 if (id == 14) tmp.event = {'cls': 'animates', 'id': 'curseNet', 'noPass': false, 'trigger': 'passNet'}; // 咒网 - if (id == 15) tmp.event = {'cls': 'animates', 'id': 'water', 'noPass': true}; // 水 // autotile: 20 diff --git a/启动服务.exe b/启动服务.exe index 69941a9a..b113ae44 100644 Binary files a/启动服务.exe and b/启动服务.exe differ diff --git a/更新内容.txt b/更新内容.txt deleted file mode 100644 index 24206cd5..00000000 --- a/更新内容.txt +++ /dev/null @@ -1,12 +0,0 @@ -新增:本地服务器 -新增:可视化地图编辑工具 -新增:便捷P图工具 √ -新增:支持Autotile √ -新增:怪物支持多属性,添加仇恨属性 √ -移除checkBlock,现在对领域和夹击无需指定坐标了 √ -新增:单向箭头、感叹号 √ -新增:勇士支持移动;其他事件支持移动 √ -快捷道具使用:1破2炸3飞;读档改为D键 √ -更多的默认素材;无需P图,直接替换即可 √ -破甲、反击、净化等效果放全局变量 √ -细节优化和Bug修复 √