9 个多月前,我们正在开发一款网页游戏,使用了 Canvas、WebSocket、CSS3 等所有优秀的 HTML5 功能。我们使用 NodeJS 和 Redis 进行大量实验代码编写。我们拥有所有优秀的脚本和插件,以及非常好的游戏架构。
但我们缺少一样东西:一个强大的编辑器。由于我们拥有 8 年的纯 Web 开发经验,我们渴望让这个编辑器也成为 Web 应用。我们不喜欢 Flash 或任何非 Web 标准或开放技术的方案,因为这会限制我们以相同的方式(平板电脑、手机、笔记本电脑、台式机)在办公室里使用我们开发的内容。
创建编辑器
因此,我们开始为我们的游戏架构师 Shay 制作 Gameleon。他玩了很多龙与地下城,也玩过很多 RPG 编辑器。我们认为他在这方面很擅长,即使没有编程知识,我们也希望给他提供制作游戏中任何内容的能力,而无需依赖我们。
我们首先制作了地图编辑器。有一点是肯定的:我们不想有任何限制,比如瓷砖。我们喜欢自己绘制地图,以任何我们认为合适的方式。你永远无法从随机图片中得知障碍物在哪里。为此,我们制作了一个多边形编辑工具,它可以定义这些障碍物。就像一个应用于地图顶部的网格,它为以后的碰撞检测提供逻辑。
这段演示如何构建地图的视频可能会更有帮助。
处理地图
在本文中,我将尝试讨论一下地图是如何处理并转换为可行走、障碍物友好的表面的。
在创建友好的区域定义方法时,使用向量是最佳选择,就像你在上面的视频中看到的那样。
地图由顶点和点定义。它们以 JSON 格式通过 WebSocket 发送到服务器。
制作优秀地图的挑战在于,如何在创建尽可能多内容的自由度与确定哪些表面是障碍物以及拥有非常快速的碰撞检测和寻路系统之间取得平衡。
我们发现,网格是这两种需求的最佳结合。使用 A* 和 QuadTree 来确定最短路径以及对象之间的碰撞(无论大小)变得简单了很多。顺便说一下,地图上定义的所有对象都被视为圆形。当我们确定一个对象是否与另一个对象发生碰撞(例如在战斗情况下)时,我们可以让其中一个对象移动到最近的空旷区域,同时仍然在目标的范围内。
因此,这就是我们将刚刚接收到的矢量地图转换为网格的原因,网格的每个方格大小为 8 x 8 个点。通过实验,我们确定这是一个足够小的方格,可以减少碰撞次数,并且在搜索长距离时仍然有效(例如,在一个 3200 x 2000 点的地图上搜索两点 A 和 B 之间的最短路径)。
确定方格
为了确定多边形将覆盖哪些方格,我们使用了一种蛮力技术。是的,这听起来很粗糙,但它非常有效。首先,我们确定多边形覆盖的矩形。
enhance.prototype._determineRectangle = function( points, poly )
{
var minX = null, minY = null, maxX = null, maxY = null;
for(var j=0;j maxX )
{
maxX = currentPoint[0];
}
if( minY == null || currentPoint[ 1 ] < minY )
{
minY = currentPoint[1];
}
if( maxY == null || currentPoint[ 1 ] > maxY )
{
maxY = currentPoint[1];
}
}
return { minX: minX, minY: minY, maxX: maxX, maxY: maxY };
}
检查点位置
获得矩形后,我们开始遍历矩形的每个点,并检查该点是在多边形内还是外。背后的理论是,绘制一条穿过该点的水平线,并检查它与多边形顶点的交点数。如果交点数为奇数,则该点在多边形外。如果交点数为偶数,则该点在多边形内。
enhance.prototype._pointInPolygon = function( x, y, poly, points )
{
var j = poly.coords.length - 1, oddNodes = false;
for(var i = 0;i < poly.coords.length; i++)
{
var pointI = poly.coords[i],
pointJ = poly.coords[j],
_pointIY = points[ pointI ][1],
_pointJY = points[ pointJ ][1],
_pointIX = points[ pointI ][0],
_pointJX = points[ pointJ ][0];
if(
_pointIX == x &&
_pointIY == y
)
{
return true;
}
if(
( _pointIY < y && _pointJY >= y ) ||
( _pointJY < y && _pointIY >= y )
)
{
if(
_pointIX + ( y - _pointIY ) / ( _pointJY - _pointIY ) * ( _pointJX - _pointIX ) < x
)
{
oddNodes = !oddNodes;
}
}
j = i;
}
return oddNodes;
}
通过这种方式,我们标记了地图的可行走区域和不可行走区域。但是,在某些特殊情况下,你可能需要动态地将某个区域标记为可行走或不可行走。
假设你有一些碎石,并且不希望该区域可行走,但一旦碎石被摧毁,该区域就必须更改。因此,我们使用一个单独的向量来存储每个多边形覆盖的实际点。
设置名称
在地图编辑器中,你可以为多边形命名,既是为了美观——例如通知玩家他们正在进入“死亡战场”——也是为了通过脚本查找。
脚本名称会自动分配——ZP01、ZP02 等——以确保名称唯一。
在脚本编辑器中,你可以使用名为 setWalkableArea 的操作,并将多边形名称作为参数发送。执行该操作时,服务器将向地图上的每个成员广播应该更改状态的点,从而轻松更新地图网格。不会进行任何额外的处理。
假设你将此操作附加到某个对象的销毁事件上,而该对象恰好位于该区域的顶部。这将产生一种错觉,即该对象本身就是障碍物,现在已被移除。
很巧妙,不是吗?
Gameleon 的一部分
以上代码是编辑器的一部分,这种编码标准贯穿整个 Gameleon 代码。
希望你喜欢这个关于我们如何看待地图和游戏创建的简短介绍。如果你想了解更多信息,请查看 Gameleon 并 关注我们的 Twitter:@gameleonMain。
完整的 源代码可在 GitHub 上获取。
关于 Victor Popescu
Gameleon 团队负责人及引擎开发者 询问我有关 Web 软件架构、游戏相关算法、NodeJS、HTML5、NoSQL 或 MySQL 的任何问题。
关于 Robert Nyman [荣誉编辑]
技术布道师及 Mozilla Hacks 编辑。发表关于 HTML5、JavaScript 和开放 Web 的演讲和博客文章。Robert 是 HTML5 和开放 Web 的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作——在瑞典和纽约市。他还在 http://robertnyman.com 上定期发布博客文章,热爱旅行和结识新朋友。
2 条评论