JS仿经典传奇游戏

这次给大家带来JS仿热血传奇游戏,JS仿热血传奇游戏的注意事项有哪些,下面就是实战案例,一起来看一下。

前言

游戏的第一个版本开发于14年,浏览器端使用html+css+js,服务端使用asp+php,通讯采用ajax,数据存储使用access+mySql。不过由于一些问题(当时还不会用node,用asp写复杂的逻辑真的会写吐;当时对canvas写的也少,dom渲染很容易达到性能瓶颈),已经废弃。后来用canvas重制了一版。本文写于18年。

JS仿经典传奇游戏 

1.开发前的准备

为什么要用Javascript来实现一款比较复杂的PC端游戏

1.js实现PC端网游是可行的。随着PC、手机硬件配置的升级和浏览器的更新换代,以及H5各种库的发展,js实现一款网游的难度越来越低。这里的难度主要是两方面:浏览器的性能;js代码是否足够易于扩展,以满足于一款逻辑极其复杂的游戏的迭代。

2.现阶段的js游戏里,很少有规模较大的可供参考。涉及到多人联机、服务端数据存储、复杂交互的游戏,大多数(几乎全部)都是用flash开发的。但是flash毕竟在衰落,而js发展迅速,并且只要有浏览器就可以运行。

为什么选择了一款2001年的热血传奇游戏

第一个原因是对老游戏的情怀; 当然更重要的另一个原因是,别的游戏要么我不会玩、要么我会玩但没有素材(图片、音效等)。花很大精力去收集一个游戏的地图、人物怪物模型、物品和装备图,然后去处理、解析一遍再用于js开发,我觉得是浪费时间。

由于我以前搜集了一些传奇游戏的素材,并且幸运地找到了提取热血传奇客户端资源文件的方法( github地址 ),所以可以直接开始写码,省去了一些准备时间。

可能的困难

1.浏览器的运行性能:这个应该是最困难的一点。假如游戏要保持40帧,那么每帧只有25ms的时间留给js计算。并且由于渲染通常比计算耗性能,实际上留给js的时间只有10毫秒左右。

2.防作弊:如何避免用户直接调用接口或者篡改网络请求数据?由于目标是用js实现比较复杂的游戏,并且任何网络游戏都需要考虑这一点,一定会有相对成熟的方案。此处不是本文重点。

2.整体设计

浏览器端

画面渲染使用canvas。

相比dom(p)+css,canvas可以处理比较复杂的场景渲染和事件管理,例如下面这个场景,涉及了四张图片:玩家、动物、地上的物品、最下层的地图图片。(实际还有地上的影子,鼠标指向人物、动物、物品时出现的相应名称,以及地面上的阴影。为了方便读懂,先不考虑这么多内容。)

JS仿经典传奇游戏 

这时,如果希望实现“点击动物、攻击动物;点击物品、捡起物品”的效果,那么需要对动物和物品进行事件监听。如果采用dom的方式,那么会出现几点难于处理的问题:

a.渲染的顺序和事件处理的顺序不同(有时候z-index小的需要先处理事件),需要额外处理。例如这个上面的例子里:点击怪物、点击物品的时候也容易点到人物,那么需要给人物做“点击事件穿透”的处理。而且事件处理的顺序不固定:假如我有一个技能(例如游戏里的治疗)需要点人物才可以释放,那么这时人物又需要有事件监听。所以一个元素是否需要处理事件、处理事件的先后,是随着游戏状态的不同而变化的,而 dom的事件绑定已经不能满足需要 。

b.有关联的元素难以放在同一个dom节点中:例如玩家的模型、玩家的名字和玩家身上的技能画效,理想情况下是放在一个

或者

容器里,便于管理(这样几个元素的定位就可以继承父元素,不用分别处理位置了)。但是这样,z-index会很难处理。例如玩家a在玩家b的上面,那么a会被b遮挡,因此需要a的z-index小一些,但是又需要让玩家a的名字不会被b的名字或者影子遮挡,就无法实现。简单点说, dom结构的可维护性会牺牲画面展示的效果,反之亦然 。

c.性能问题。即使牺牲了效果,用dom渲染,势必出现很多嵌套关系,所有元素的style都在频繁变化,连续触发浏览器的repaint甚至reflow。

canvas渲染逻辑与项目逻辑分离

如果将canvas的各种渲染操作(如 drawImage 、 fillText 等)与项目代码放在一起,那么势必导致项目后期无法维护。翻了一下几款现有的canvas库,结合vue的数据绑定+调试工具的方式,搞了一个全新的canvas库Easycanvas( github地址 ),并且像vue一样支持通过一个插件来调试canvas中的元素。

这样,整个游戏的渲染部分就容易很多,只需要管理游戏当前的状态、并且根据服务端从socket传回来的数据去更新数据就可以。 “数据的变化引起视图的变化”这个环节由Easycanvas负责 。例如下图的玩家包裹物品的实现,我们只需要给出包裹容器的位置、背包里每个元素的排布规则,然后将每个包裹的物品绑定到一个array上,然后去管理这个array即可(数据映射到画面的过程由Easycanvas负责)。

JS仿经典传奇游戏 

例如,5行8列共计40个物品的样式可以通过如下的形式传递给Easycanvas(index为物品索引,物品x方向间距36,y方向间距32)。而这个逻辑是一成不变的,无论物品的数组怎样变化、包裹被拖拽到什么位置,每个物品的相对位置都是固定的。至于canvas上的渲染则完全不需要项目本身来考虑,所以可维护性较好。

style: { tw: 30, th: 30, tx: function () { return 40 + index % 8 * 36; }, ty: function () { return 31 + Math.floor(index / 8) * 32; }}

登录后复制

canvas分层渲染

假设:游戏需要保持40帧,浏览器宽800高600,面积48万(后面称48万为1个屏幕面积)。

如果用同一个canvas来呈现,那么这个canvas的帧数40,每秒至少需要绘制40个屏幕面积。但是同一个坐标点很可能出现多个元素重叠的情况,例如底部的UI、血条、按钮就是重叠放置,他们又共同遮挡了场景地图。所以这些加在一起,每秒浏览器的绘制量很容易达到100个屏幕面积以上。

这个绘制是很难优化的,因为整个canvas画布的任何一处都在进行视图的更新:可能是玩家和动物的移动、可能是按钮的特效、可能是某个技能效果的变化。这样的话,即使玩家不动,由于衣服“随风飘飘”的效果(其实是精灵动画播放到下一张图),或者是地面上出现了一瓶药水,都要引起整个canvas的重绘。因为 游戏中几乎不可能出现某一帧的画面与上一帧毫无区别的情况,即使是游戏画面的一个局部,也很难保持不变 。整个游戏的画面永远在更新。

因为 游戏中几乎不可能出现某一帧的画面与上一帧毫无区别的情况 ,画面永远在更新。

因此,这次我采用了3个canvas重叠排布的方式。由于Easycanvas的事件处理支持传递,因此即使点到了最上面的canvas,如果没有任何元素结束了某一次点击,后面的canvas也可以接到这次事件。3个canvas分别负责UI、地面(地图)、精灵(人物、动物、技能特效等):

JS仿经典传奇游戏 

这样分层的好处是,每层最大帧数可以根据需要来调整:

例如UI层,因为很多UI平时是不动的,即使动也不会需要太精密的绘制,所以可以适当降低帧数,例如降低到20。这样假如玩家的体力从100降低到20,那么可以在50ms内更新视图,而50ms的切换是玩家感觉不出来的。因为像体力这种 UI层数据的变化很难在很短的时间内连续变化多次,而50ms的延迟是人很难感知的,所以不需要频繁的绘制 。假如我们每秒节约了20帧,那么很可能可以节约10个屏幕面积的绘制。

再如地面,只有玩家移动的时候,地图才会变化。这样,如果玩家不动,那么每帧可以省去1个屏幕面积。由于需要保证玩家移动时的流畅感,地面的最大帧数不宜太低。假如地面为30帧,那么玩家不动时,每秒就可以节约30个屏幕面积的绘制(这个项目中,地图是几乎绘满屏幕的)。而且其它玩家、动物的移动不会改变地面,也不需要重绘地面这一层。

精灵层最大帧数不能降低,这层会展示游戏的人物动作等核心部分,所以最大帧数设置为40.

这样,每秒绘制的面积,玩家移动时可能是80~100个屏幕面积,而玩家不移动时可能只有50个屏幕面积。游戏中,玩家停下来打怪、打字、整理物品、释放技能都是站立不动的,因此 大量的时间里都不会触发地面的绘制,对性能的节约很大 。

服务器端

由于目标是js实现一款多人网游,所以服务端使用Node,使用socket与浏览器通讯。这样做还有一个好处,就是一些 公用的逻辑可以在两端复用 ,例如判断地图上某个坐标点是否存在障碍物。

Node端的玩家、场景等游戏相关数据全部存储与内存中,定期同步至文件。每次Node服务启动时,将数据从文件读取至内存。这样可以玩家较多时,文件读写的频率成指数级上升,从而引发的性能问题。(后来为了提高稳定,为文件读写增加了一个缓冲,“内存-文件-备份”的方式,以免读写过程中服务器重启导致的文件损坏)。

Node端分接口、数据、实例等多层。“接口”负责和浏览器端交互。“数据”是一些静态数据,例如某个药品的名称和效果、某个怪物的速度和体力,是游戏规则的一部分。“实例”是游戏中的当前状态,例如某个玩家身上的一个药品,就是“药品数据”的一个实例。再举个例子,“鹿的实例”拥有“当前血量”这个属性,鹿A可能是10,鹿B可能是14,而“鹿”本身只有“初始血量”。

3.场景地图的实现

地图场景

下面开始介绍地图场景部分,仍然是依赖 Easycanvas 进行渲染。

思考

由于玩家是始终固定在屏幕中心的,所以玩家的移动,实际上是地图的移动。例如玩家像左跑,地图就向右平移即可。刚才已经提到,玩家处于3个canvas中的中间一层,而地图属于底层,因此玩家一定遮挡地图。

这样看起来是合理的,但是假如地图中有一棵树,那么“玩家的层次始终高于树”就不对了。这时,有2种大的解决方案:

地图分层,“地面”与“地上”拆开。将玩家处于两层之间,例如下图,左侧是地上、右侧是地面,然后重叠绘制,把人物夹在中间:

JS仿经典传奇游戏 

这样看似解决了问题,其实引入了2个新的问题:第一个是,玩家有时可能会被“地上”的东西遮挡(例如一棵树),有时又需要能够遮挡“地上”的东西(例如站在这棵树的下方,头部会遮挡住树)。另一个问题是渲染的性能消耗会增加。由于玩家是时刻在变的,“地上”这一层需要频繁重绘。这样做也打破了最初的设计——尽量节约地面大地图的渲染,从而导致canvas的分层更加复杂。

地图不分层,“地面”与“地上”在一起绘制。当玩家处于树后的时候,将玩家的透明度设置为0.5,例如下图:

JS仿经典传奇游戏 

这样做只有一个坏处:玩家的身体要么都不透明、要么都半透明(怪物在地图上行走也会有这个效果),不会完全真实。因为理想的效果是存在玩家的身体被遮挡住一部分的场景的。但是这样做对性能友好,并且代码易于维护,目前我也采用了这个方案。

那么如何判断“地图”这张图片哪些地方是树呢?游戏通常会有一个大的地图描述文件(其实就是一个Array),通过0、1、2这样的数字来标识哪些地方可以通过、哪些地方存在障碍物、哪些地方是传送点等等。热血传奇中的这个“描述文件”就是48×32为最小单位进行描述的,所以玩家在传奇中的行动会有一种“棋盘”的感觉。单位越小越流畅,但是占用的体积越大、生成这个描述的过程也就越耗时。

下面开始正题。

实现

我找了一个朋友帮我导出热血传奇客户端中“比奇省”的地图,宽33600、高22400,是我电脑的几百倍大。为了避免电脑爆炸,需要拆分成多块加载。由于传奇的最小单元是48×32,我们以480×320将地图拆成了4900(70×70)个图片文件。

canvas的尺寸我们设定为800×600,这样玩家只需要加载3×3共计9张图片就可以铺满整个画布。800/480=1.67,那么为什么不是2×2?因为有可能玩家当前的位置正好导致有的图片只展示了一部分。如下图:

JS仿经典传奇游戏 

相信看了本文案例你已经掌握了方法,更多精彩请关注【创想鸟】其它相关文章!

推荐阅读:

webpack-dev-server怎么设置远程模式

在ES6中子组件怎么调用父组件

以上就是JS仿经典传奇游戏的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2781314.html

(0)
上一篇 2025年3月8日 16:14:55
下一篇 2025年3月8日 16:15:06

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • js数据结构和算法之数组和散列表详解

    一.数据结构 1.什么是数据结构数据结构就是关系,没错,就是数据元素相互之间存在的一种或多种特定关系的集合。传统上,我们把数据结构分为逻辑结构和物理结构。逻辑结构:是指数据对象中数据元素之间的相互关系,也是我们今后最需要关注和讨论的问题。物…

    2025年3月8日
    200
  • 学习了解javascript中script的标签属性

    JavaScript中的script标签主要是用来引入js代码的。script标签引入js代码的方式有两种,下面跟随小编一起来了解一下吧。 一、直接写在中间 二、引入外部的js文件。 引入方式一: 立即学习“Java免费学习笔记(深入)”;…

    2025年3月8日 编程技术
    200
  • JavaScript工厂模式、原型模式、构造器模式

    一.什么是模式.模式是一种可复用的解决方案,而反模式呢就是针对某个问题的不良解决方案。 js反模式常见例子 ①向setTimeout和setInterval传递字符串,而不是函数,这会触发eval()的内部使用。②在全局上下文中定义大量的变…

    编程技术 2025年3月8日
    200
  • js高性能函数防抖与节流详解

    函数节流是指一定时间内js方法只跑一次,函数防抖:是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。本文主要和大家分享js高性能函数防抖与节流详解,希望能帮助到大家。 一.函数节流(throttle) 1.函数节流的目的例如,DOM…

    2025年3月8日
    200
  • JavaScript的json格式实例详解

    json的形式是用大括号“{}”包围起来的项目列表,每一个项目间用逗号(,)分隔,而项目就是用冒号(:)分隔的属性名和属性值。这是典型的字典表示形式,也再次表明javascript里的对象就是字典结构。不管多么复杂的对象,都可以用一句jso…

    2025年3月8日
    200
  • javascript常用工具类的封装总结

    本文主要和大家分享javascript常用工具类的封装总结,希望能帮助到大家。 一.javaScript 1. type 类型判断 isString (o) { //是否字符串    return Object.prototype.toSt…

    编程技术 2025年3月8日
    200
  • JavaScript正则表达式小技巧

    这次给大家带来JavaScript正则表达式小技巧,使用JavaScript正则表达式的注意事项有哪些,下面就是实战案例,一起来看一下。 正则表达式作为一种匹配处理字符串的利器在很多语言中都得到了广泛实现和应用.这篇文章主要介绍了JavaS…

    编程技术 2025年3月8日
    200
  • 原生js怎么调用json

    这次给大家带来原生js怎么调用json,原生js调用json的注意事项有哪些,下面就是实战案例,一起来看一下。 Ajax基础 ajax:无刷新数据读取,读取服务器上的信息 HTTP请求方法: GET:用于获取数据,如浏览帖子 ajax.ju…

    编程技术 2025年3月8日
    200
  • JavaScript中的数据截取问题详解

    本文主要和大家分享JavaScript中的数据截取问题详解,希望能帮助到大家。 一.数组中获取 1.获取value的值 var revokeModel = $(“#modalMsgRevoke form”).seri…

    2025年3月8日 编程技术
    200
  • JS实现默认头像填充

    这次给大家带来JS实现默认头像填充,JS实现默认头像填充的注意事项有哪些,下面就是实战案例,一起来看一下。 在我的不少项目中,都有缺省头像的问题。为了保持个性和方便辨认,会给没有头像的用户填充带名字的头像。 代码分享:https://git…

    2025年3月8日
    200

发表回复

登录后才能评论