如何创建"Be the Thief" 体验

分享于 

21分钟阅读

Web开发

  繁體

在10天内开发 Windows 8应用程序。

介绍

'小偷小偷' 是由 Skybound漫画系列出版的新的图形系列,是流行系列的Walking行尸走肉 Walking。 偷偷的小偷是of最大的世界偷窃者的故事,他们开始偷救他人的错误。 AMC,行尸走肉节目,目前开发基于小偷小偷的节目。

IE10: Thief Thief Skybound publicizes modern modern modern modern modern modern modern modern modern modern modern。

我们与偷盗者经验的目标是通过 IE 10等于一个本地应用程序中的一个来提供沉浸体验。 我们使用最新的HTML5.CSS3。触摸和相关技术来实现这一点。

利用 IE 10硬件加速加工和行业领先的触摸支持,使用户在小偷的kirkman小偷。

完美的触摸

IE 提高了对指针事件的浏览器支持,我们充分利用了这个小偷体验的偷偷者。 例如用户可以使用多触摸旋转拨号来练习他们的安全破解技能。 使用新提交的指针事件API来支持浏览器的Internet支持,使我们能够建立大量的触摸体验。

让我们看看( 在 CoffeeScript 及其呈现的Javascript中):

CoffeeScript
@wheel = $('#Dial')
@angle = 0if window.MSGesture
 gesture = new MSGesture()
 gesture.target = @wheel[0]
 @wheel.bind("MSGestureChange", @didRotate).bind("MSPointerDown", @addGesturePointer)else @wheel.mousedown(@startRotate)
 $(window).mousemove(@didRotate).mouseup(@stopRotate)
addGesturePointer:(e)=> #Handle Touch v. Mouse in IE10
 if e.originalEvent.pointerType!= 2 #Not Touch Input
 e.target.msSetPointerCapture(e.originalEvent.pointerId)
 @startRotate(e.originalEvent)
 @gesture = false $(window).bind("MSPointerMove", @didRotate).bind("MSPointerUp", @stopRotate)
 else @gesture && @gesture.addPointer(e.originalEvent.pointerId)
startRotate: (e)=> document.onselectstart = (e)-> false @rotating = true currentPoint = screenPointToSvg(e.clientX, e.clientY, @wheel[0])
 angle = Math.atan2(currentPoint[1] - @center_y + 55, currentPoint[0] - @center_x + 90)
 angle = angle * LockpickGame.rad2degree
 @mouseStartAngle = angle
 @dialStartAngle = @angle
didRotate: (e)=>return unless @rotating
 if @gesture
 @angle += e.originalEvent.rotation * LockpickGame.rad2degree
 elseif e.originalEvent.pointerType
 e = e.originalEvent
 currentPoint = screenPointToSvg(e.clientX, e.clientY, @wheel[0])
 angle = Math.atan2(currentPoint[1] - @center_y - 20, currentPoint[0] - @center_x + 0)
 angle = angle * LockpickGame.rad2degree
 @angle = @normalizeAngle(@dialStartAngle)+(@normalizeAngle(angle) - @normalizeAngle(@mouseStartAngle))
 @center_x = 374.3249938 @center_y = 354.7909851 rotate_transform = "rotate(#{@angle} #{@center_x} #{@center_y})" requestAnimationFrame(()=> @wheel.attr("transform", rotate_transform)
 )
stopRotate: (e)=> document.onselectstart = (e)-> true @rotating = false
Javascript
var gesture,
 _this = this;this.wheel = $('#Dial');this.angle = 0;if (window.MSGesture) {
 gesture = new MSGesture();
 gesture.target = this.wheel[0];
 this.wheel.bind("MSGestureChange", this.didRotate).bind("MSPointerDown", this.addGesturePointer);
} else {
 this.wheel.mousedown(this.startRotate);
 $(window).mousemove(this.didRotate).mouseup(this.stopRotate);
}
({
 addGesturePointer: function(e) {
 if (e.originalEvent.pointerType!== 2) {
 e.target.msSetPointerCapture(e.originalEvent.pointerId);
 _this.startRotate(e.originalEvent);
 _this.gesture = false;
 return $(window).bind("MSPointerMove", _this.didRotate).bind("MSPointerUp", _this.stopRotate);
 } else {
 return _this.gesture && _this.gesture.addPointer(e.originalEvent.pointerId);
 }
 },
 startRotate: function(e) {
 var angle, currentPoint;
 document.onselectstart = function(e) {
 returnfalse;
 };
 _this.rotating = true;
 currentPoint = screenPointToSvg(e.clientX, e.clientY, _this.wheel[0]);
 angle = Math.atan2(currentPoint[1] - _this.center_y + 55, currentPoint[0] - _this.center_x + 90);
 angle = angle * LockpickGame.rad2degree;
 _this.mouseStartAngle = angle;
 return _this.dialStartAngle = _this.angle;
 },
 didRotate: function(e) {
 var angle, currentPoint, rotate_transform;
 if (!_this.rotating) {
 return;
 }
 if (_this.gesture) {
 _this.angle += e.originalEvent.rotation * LockpickGame.rad2degree;
 } else {
 if (e.originalEvent.pointerType) {
 e = e.originalEvent;
 }
 currentPoint = screenPointToSvg(e.clientX, e.clientY, _this.wheel[0]);
 angle = Math.atan2(currentPoint[1] - _this.center_y - 20, currentPoint[0] - _this.center_x + 0);
 angle = angle * LockpickGame.rad2degree;
 _this.angle = _this.normalizeAngle(_this.dialStartAngle) + (_this.normalizeAngle(angle) - _this.normalizeAngle(_this.mouseStartAngle));
 }
 _this.center_x = 374.3249938;
 _this.center_y = 354.7909851;
 rotate_transform = "rotate(" + _this.angle + "" + _this.center_x + "" + _this.center_y + ")";
 return requestAnimationFrame(function() {
 return _this.wheel.attr("transform", rotate_transform);
 });
 },
 stopRotate: function(e) {
 document.onselectstart = function(e) {
 returntrue;
 };
 return _this.rotating = false;
 }
});

实际上,我们将 MSGestureChange 事件绑定到 didRotate 函数。 如果玩家使用触摸而不是鼠标,我们只需要三行来计算和移动表盘,而不是传统鼠标指针的八行。 IE 负责其他的动作,包括惯性运动。 手势API同时返回一个角度和惯性值,使一切结合在一起。

触摸几乎所有的经验,从绘制玩家的角色到pickpocketing和导航游戏的终端游戏。 Internet chrome少于浏览器,浸浸式,全屏模式的Windows 8,用户更充分地进入操作,非常像本机应用。

向量的欢乐

计算,特别是网络,朝着一个独立的世界发展。 你的网站或者应用程序需要查看从低到高的屏幕,矢量图形和 SVG,成为开发人员的关键工具。

SVG允许直接在代码中表达图像,这使得图像无需任何丢失的质量或者保真度就可以无限。 在 IE 中,可以将筛选器应用到你的SVG,使它的容易地添加复杂的影像和模糊的复杂效果。

代码 below 是对右边的星星图像的SVG代码。 <多边形> 元素绘制实际的星形,然后将筛选器属性设置为 <筛选器> 元素的to。

<?xmlversion="1.0"encoding="utf-8"?><!DOCTYPEsvgPUBLIC"-//W3C//DTDSVG1.1//EN""http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svgversion="1.1"id="Layer_1"xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"x="0px"y="0px"width="227px"height="197px"viewBox="0 0 227 197"enable-background="new 0 0 227 197"xml:space="preserve"><defs><!-- Placed just after your opening svg element --><filterwidth="200%"height="200%"x="0"y="0"id="blurrydropshadow"><feOffsetin="SourceAlpha"result="offOut"dy="10"dx="10"></feOffset><feGaussianBlurin="offOut"result="blurOut"stdDeviation="7"></feGaussianBlur><feBlendin="SourceGraphic"in2="blurOut"mode="normal"></feBlend></filter><filterwidth="200%"height="200%"x="0"y="0"id="soliddropshadow"><feOffsetin="SourceAlpha"result="offOut"dy="10"dx="10"></feOffset><feBlendin="SourceGraphic"in2="offOut"mode="normal"></feBlend></filter></defs><polygonfill="#4C5BA9"filter="url(#soliddropshadow)"stroke="#000000"stroke-width="4"stroke-miterlimit="10"points="113.161,24.486 135.907,70.576
186.77,77.966 149.965,113.843 158.654,164.5 113.161,140.583 67.667,164.5 76.356,113.843 39.552,77.966 90.414,70.576"/></svg>

我们还发现,IE 在速度和平滑性方面运行得更好,包括顶级SVG元素,比 IE 提供的硬件加速。

图像作为代码

使用SVG的一个很好的方法是,它们很容易移动。编辑和保存,因为它们本机表示为文本。 这在别名创建者中有很大的效果,我们为每个可以能的角色提供了外部 SVG。 然后将所有这些显示为可以编辑的图形 Surface,允许用户添加特性和附件以及使用 cursor 或者手指。

一旦用户创建了他们的别名,SVG数据就会使用of来保存。 此时,用户的别名可以在站点的任何地方使用。 这个别名只是从本地存储提取,并用作其他图像,如果用户需要编辑它的别名,就会变得很简单: 加载已经创建的字符,加载编辑器,然后创建 !

优化

在使用SVG时需要考虑的一个重要问题是,在向矢量艺术应用程序导出图形之后可以对它们进行大量的优化。 大多数程序如,和,都会在不需要存在的文件中保留很多元素,而且不会丢失任何图像定义。 这个站点是我们第一个大型SVG任务之一,在找出合适的SVG工作流时我们了解了很多。 我们使用 inline SVG和SVG作为 <to> 标签,需要采取几种不同的优化方法。

对于那些不需要内部动画的艺术作品,我们可以通过一系列的SVG清理软件,比如 CleanSVG 或者 Scour来运行所有的artwork。 为了获得更精确的数据,我们需要采用更加细致的方法,移除额外的组,并在向量编辑器中进行路径简化,以确保我们没有丢失脚本所需的身份属性。

前端技术

我们曾经提到 上面,大部分项目是用CoffeeScript和常规JavaScript结合编写的。

正如你在代码段中所看到的,to允许我们以更简单的方式编写 JavaScript,但是在浏览器中。

我们试图用基于web标准的开发在新兴web技术中实验,因为在今天的最后,( 引用CoffeeScript文档) 只是 JavaScript。

在CSS方面,我们还采用了 LESS ( )。 CoffeeScript类似,LESS 是样式表的预处理器,它使我们能够构建更加灵活和可以维护的CSS。 它的变量。mixin和操作的增加证明了。

魔术包

在"抢劫"里我们的小偷必须在没有被检测到的情况下。 在后面的级别中,需要被偷偷的( 在水平地图上使用简单的双击) 干扰,这样玩家可以安全地退出水平。 我们需要一个算法来映射一个路径围绕水平的守护人跟踪和调查被盗者触发的事件。

一个星( A* ) - 一个路径算法

一个作为A*的星,也是目前存在的图形最常见的路径寻找算法。 在某些情况下,A*保证路径存在时总是找到最短路径,如果没有障碍,则不可以能从A 点移动。 它通常用于游戏,机器人等。

下面是维基百科的A*条目,其中包含了它的伪代码: http://en.wikipedia.org/wiki/A_star

为什么 A*?

这种情况下很容易使用A*因为地图已经是网格,这只是一种图形,地图不包含任何特殊的对象,如电梯。teleporters 等等,可能会破坏它的颜色。 为了满足这些类型的对象,我们必须使用另一种类似Djisktra算法的算法。

实现的挑战

在使用A*算法为我们的AI安全防护程序创建路径时,我们遇到了一些对数据结构支持的挑战。 A*实现通常需要几个在JavaScript中不存在的数据结构。 例如,我们将它的中的一些内部列表表示为哈希表,而JS只能使用对象的字符串。

另一个问题是需要优先队列。 为了效率,A*需要有一个节点队列,它可以通过"最佳"候选节点进行排序。 这种思想由优先队列表示,队列通常作为二进制堆实现。 这个数据结构在JS中不存在,所以我们基于经典的伪代码来实现它"。算法介绍。"本书是关于这类主题的圣经 for。

只有 200行代码

我们的A*算法的JavaScript注入为偷偷者的小偷提供了 200行代码,包括注释 ! 为了进一步了解路径发现和人工智能,我们邀请人们对 astar.js 有兴趣,并阅读上面提到的Wikipedia文章。

* A* pathfinding algorithm implementation based on the the pseudo-code
* from the Wikipedia article (http://en.wikipedia.org/wiki/A_star)* Date: 02/05/2013
*/"use strict";// Node of the search graphfunction GraphNode(x, y) {
 this.x = x;
 this.y = y;
 this.hash = x + "" + y;
 this.f = Number.POSITIVE_INFINITY;
}// Constructor for the pathfinder. It takes a grid which is an array of arrays// indicating whether a position is passable (0) or it's blocked (1)function AStar(grid) {
 // Compares to nodes by their f value function nodeComparer(left, right) {
 if (left.f > right.f) {
 return1;
 }
 if (left.f < right.f) {
 return -1;
 }
 return0;
 }
 // Manhattan heuristic for estimating the cost from a node to the goal function manhattan(node, goal) {
 return Math.abs(node.x - goal.x) + Math.abs(node.y - goal.y);
}// Gets all the valid neighbour nodes of a given node (diagonals are not// neighbours)function getNeighbours(node) {
 var neighbours = [];
 for (var i = -1; i <2; i++) {
 for (var j = -1; j <2; j++) {
 // Ignore diagonalsif (Math.abs(i) === Math.abs(j)) {
 continue;
 }
 // Ignore positions outside the gridif (node.x + i <0 || node.y + j <0 ||
 node.x + i >= grid[0].length || node.y + j >= grid.length) {
 continue;
 }
 if (grid[node.y + j][node.x + i] === 0) {
 neighbours.push(new GraphNode(node.x + i, node.y + j));
 }
 }
 }
 return neighbours;
}// Builds the path needed to reach a target node from the pathfinding informationfunction calculatePath(parents, target) {
 var path = [];
 var node = target;
 while (typeof node!== "undefined") {
 path.push([node.x, node.y]);
 node = parents[node.hash];
 }
 return path.reverse();
}// Searches for a path between two positions in a gridthis.search = function(start, end) {
 var fCosts = new BinaryHeap([], nodeComparer);
 var gCosts = {};
 var colors = {};
 var parents = {};
 // Initializationvar node = new GraphNode(start[0], start[1]);
 var endNode = new GraphNode(end[0], end[1]);
 node.f = manhattan(node, endNode);
 fCosts.push(node);
 gCosts[node.hash] = 0;
 while (!fCosts.isEmpty) {
 var current = fCosts.pop();
 // Have we reached our goal?if (current.x === endNode.x && current.y === endNode.y)
 {
 return calculatePath(parents, endNode);
 }
 // Mark the node as visited (Black), and get check it's neighbours colors[current.hash] = "Black";
 var neighbours = getNeighbours(current);
 for (var i = 0; i < neighbours.length; i++)
 {
 var neighbour = neighbours[i];
 if (colors[neighbour.hash] === "Black") {
 continue;
 }
 // If we had not visited the neighbour before, or we have found a faster way to visit the neighbour, then update g and calculate fif (typeof gCosts[neighbour.hash]!== "Undefined" || gCosts[current.hash] + 1< gCosts[neighbour.hash]) {
 parents[neighbour.hash] = current;
 gCosts[neighbour.hash] = gCosts[current.hash] + 1;
 neighbour.f = gCosts[neighbour.hash] + manhattan(neighbour, endNode);
 // Neighbour not visited before, mark it as a potential candidate ("Gray")if (typeof colors[neighbour.hash]!== "Undefined") {
 colors[neighbour.hash] = "Gray";
 fCosts.push(neighbour);
 }
 else { // We have found a better way to reach this node fCosts.decreaseItem(neighbour);
 }
 }
 }
}return [];
};
}

IE 10新建

IE 10充满了api的新特性,允许像expereince这样的更原生应用。 below 是我们在这个经验中使用的一个列表,以及它们如何 benefitted。

  • 使用指针对象来检测用户输入。处理鼠标。笔和触摸事件的所有交互使用指针对象。
  • 使用指针 API 使用指针 API,在站点中使用指针API来处理站点的更高级指针交互,并绘制Heist的路径,然后绘制路径。
  • 在网站中,为一些较大的过渡动画( 在页面之间和behind-the-scenes部分之间) 使用了的动画,其中包括动画动画。 这使得浏览器可以处理动画计算而不是用JavaScript编程。
  • 收费 允许我们在浏览器认为最合适的时候获得动画帧,如果浏览器 bogged,可能会把帧放在一起,如果浏览器陷入停滞状态。 这在整个体验中被用来提供最流畅的动画。
  • 使用 API - - 我们可以检测到站点什么时候不再是当前可见的浏览器选项卡。 这允许我们管理一些不需要运行的应用程序,当用户没有积极使用站点时。 我们主要使用这里功能在不可见时静音站点的音频。
  • 为site的部分动态创建的部分创建了 SVG过滤器 - SVG过滤器,以创建对某些动态创建的部分的效果。 这些主要用于添加与在代码中创建的项目相匹配的投影阴影,例如在炸弹游戏中画的线。
  • 为网站的各个地点提供了 setImmediate API API,以提高站点和功耗的性能。 它被用作计时器,就像使用ipqos或者to一样,在CPU准备好处理它的时候增加了一些好处。 这平衡了速度和功耗。 它在保存配置文件数据或者需要尽快发生的它的他项目中使用,以保持用户体验顺利。

本文是来自 IE 团队的HTML5技术系列的一部分。 在本文中试用 3个月的免费 BrowserStack 跨浏览器 测试 @ http://modern.IE


EXP  
相关文章