案例研究:将 Wordico 从 Flash 转换为 HTML5

发布于 2023-05-27 22:10:19 字数 12183 浏览 67 评论 0

当我们将 Wordico 填字游戏从 Flash 转换为 HTML5 时,我们的首要任务是忘却我们所知道的关于在浏览器中创建丰富的用户体验的一切知识。 虽然 Flash 为应用程序开发的所有方面(从矢量绘图到多边形命中检测到 XML 解析)提供了单一、全面的 API,但 HTML5 提供了各种浏览器支持的混乱规范。

我们还想知道 HTML(一种特定于文档的语言)和 CSS(一种以框为中心的语言)是否适合构建游戏。 游戏会像在 Flash 中那样在浏览器中统一显示吗?它的外观和行为会不会一样好? 对于 Wordico,答案是 肯定的。

你的载体是什么?Wordico?

我们仅使用矢量图形开发了 Wordico 的原始版本:直线、曲线、填充和渐变。 结果既高度紧凑又可无限扩展:

Wordico 线框
在 Flash 中,每个显示对象都是由矢量形状组成的。

我们还利用 Flash 时间轴来创建具有多个状态的对象。 例如,我们使用了九个命名的关键帧 Space目的:

Flash 中的三字母空格。

然而,在 HTML5 中,我们使用位图精灵:

显示所有九个空间的 PNG 精灵。

为了从各个空间创建 15x15 游戏板,我们迭代了一个 225 个字符的字符串符号,其中每个空间由不同的字符表示(例如“t”代表三个字母,“T”代表三个单词)。 这在 Flash 中是一个简单的操作; 我们只是简单地划掉空格并将它们排列在一个网格中:

var spaces:Array = new Array();

for (var i:int = 0; i < 225; i++) {
  var space:Space = new Space(i, layout.charAt(i));
  ...
  spaces.push(addChild(space));
}

LayoutUtil.grid(spaces, 15);

在 HTML5 中,它有点复杂。 我们使用 <canvas>element ,位图绘图表面,一次绘制一个方格的游戏板。 第一步是加载图像精灵。 加载后,我们遍历布局符号,每次迭代绘制图像的不同部分:

var x = 0;  // x coordinate
var y = 0;  // y coordinate
var w = 35; // width and height of a space

for (var i = 0; i < 225; i++) {
  if (i && i % 15 == 0) {
    x = 0;
    y += w;
  }

  var imageX = "_dDFtTqQxm".indexOf(layout.charAt(i)) * 70;

  canvas.drawImage("spaces.png", imageX, 0, 70, 70, x, y, w, w);

  x += w;
}

这是网络浏览器中的结果。 请注意画布本身有一个 CSS 投影:

在 HTML5 中,游戏板是一个单一的画布元素。

转换 tile 对象是一个类似的练习。 在 Flash 中,我们使用 文本字段 和矢量形状:

Flash 磁贴是文本字段和矢量形状的组合。

在 HTML5 中,我们将三个图像精灵组合在一个 <canvas>运行时元素:

HTML 磁贴是三个图像的组合。

现在我们有 100 张画布(每个图块一张)加上游戏板的画布。 这是“H”磁贴的标记:

<canvas width="35" height="35" class="tile tile-racked" title="H-2"/>

这是相应的 CSS:

.tile {
  width: 35px;
  height: 35px;
  position: absolute;
  cursor: pointer;
  z-index: 1000;
}

.tile-drag {
  -moz-box-shadow: 1px 1px 7px rgba(0,0,0,0.8);
  -webkit-box-shadow: 1px 1px 7px rgba(0,0,0,0.8);
  -moz-transform: scale(1.10);
  -webkit-transform: scale(1.10);
  -webkit-box-reflect: 0px;
  opacity: 0.85;
}

.tile-locked {
  cursor: default;
}

.tile-racked {
  -webkit-box-reflect: below 0px -webkit-gradient(linear, 0% 0%, 0% 100%,  
    from(transparent), color-stop(0.70, transparent), to(white));
}

我们在瓷砖被拖动时(阴影、不透明度和缩放)以及当瓷砖放在架子上时(反射)应用 CSS3 效果:

拖动的图块稍大,略透明,并有阴影。

使用光栅图像有一些明显的优势。 首先,结果是像素级的。 其次,这些图像可以被浏览器缓存。 第三,通过一些额外的工作,我们可以换掉图像来创建新的瓷砖设计——比如金属瓷砖——而且这个设计工作可以在 Photoshop 中完成,而不是在 Flash 中。

不足之处? 通过使用图像,我们放弃了对文本字段的编程访问。 在 Flash 中,更改类型的颜色或其他属性是一个简单的操作; 在 HTML5 中,这些属性被嵌入到图像本身中。 (我们尝试过 HTML 文本,但它需要大量额外的标记和 CSS。我们也尝试过 canvas 文本,但结果在不同浏览器之间不一致。)

模糊逻辑

我们希望充分利用任何尺寸的浏览器窗口,并避免滚动。 这在 Flash 中是一个相对简单的操作,因为整个游戏都是用矢量绘制的,并且可以在不失保真度的情况下按比例放大或缩小。 但它在 HTML 中比较棘手。 我们尝试使用 CSS 缩放,但最终得到了一个模糊的画布:

CSS 缩放(左)与重绘(右)。

我们的解决方案是在用户调整浏览器大小时重新绘制游戏板、机架和图块:

window.onresize = function (evt) {
  ...
  gameboard.setConstraints(boardWidth, boardWidth);

  ...
  rack.setConstraints(rackWidth, rackHeight);

  ...
  tileManager.resizeTiles(tileSize);
});

我们最终在任何屏幕尺寸下都得到了清晰的图像和令人愉悦的布局:

游戏板填充垂直空间; 其他页面元素围绕它流动。

开门见山

由于每个板块都是绝对定位的,并且必须与游戏板和架子精确对齐,因此我们需要一个可靠的定位系统。 我们使用两个函数, BoundsPoint, 以帮助管理元素在全局空间(HTML 页面)中的位置。 Bounds描述了页面上的一个矩形区域,而 Point描述 x,y坐标,也称为注册点。 相对于页面左上角 (0,0) 的

Bounds,我们可以检测两个矩形元素的交集(例如瓷砖穿过货架时)或者矩形区域(例如双字母空间)是否包含任意点(例如瓷砖的中心点)。 下面是 Bounds 的实现:

// bounds.js
function Bounds(element) {
  var x = element.offsetLeft;
  var y = element.offsetTop;
  var w = element.offsetWidth;
  var h = element.offsetHeight;

  this.left = x;
  this.right = x + w;
  this.top = y;
  this.bottom = y + h;
  this.width = w;
  this.height = h;
  this.x = x;
  this.y = y;
  this.midx = x + (w / 2);
  this.midy = y + (h / 2);
  this.topleft = new Point(x, y);
  this.topright = new Point(x + w, y);
  this.bottomleft = new Point(x, y + h);
  this.bottomright = new Point(x + w, y + h);
  this.middle = new Point(x + (w / 2), y + (h / 2));
}

Bounds.prototype.contains = function (point) {
  return point.x > this.left &&
    point.x < this.right &&
    point.y > this.top &&
    point.y < this.bottom;
}

Bounds.prototype.intersects = function (bounds) {
  return this.contains(bounds.topleft) ||
    this.contains(bounds.topright) ||
    this.contains(bounds.bottomleft) ||
    this.contains(bounds.bottomright) ||
    bounds.contains(this.topleft) ||
    bounds.contains(this.topright) ||
    bounds.contains(this.bottomleft) ||
    bounds.contains(this.bottomright);
}

Bounds.prototype.toString = function () {
  return [this.x, this.y, this.width, this.height].join(",");
}

我们用 Point确定页面上任何元素或鼠标事件的绝对坐标(左上角)。 Point还包含计算距离和方向的方法,这是创建动画效果所必需的。 下面是实现 Point:

// point.js

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.distance = function (point) {
  var a = point.x - this.x;
  var b = point.y - this.y;

  return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}

Point.prototype.distanceX = function (point) {
  return Math.abs(this.x - point.x);
}

Point.prototype.distanceY = function (point) {
  return Math.abs(this.y - point.y);
}

Point.prototype.interpolate = function (point, pct) {
  var x = this.x + ((point.x - this.x) * pct);
  var y = this.y + ((point.y - this.y) * pct);

  return new Point(x, y);
}

Point.prototype.offset = function (x, y) {
  return new Point(this.x + x, this.y + y);
}

Point.prototype.vector = function (point) {
  return new Point(point.x - this.x, point.y - this.y);
}

Point.prototype.toString = function () {
  return this.x + "," + this.y;
}

// static
Point.fromElement = function (element) {
  return new Point(element.offsetLeft, element.offsetTop);
}

// static
Point.fromEvent = function (evt) {
  return new Point(evt.x || evt.clientX, evt.y || evt.clientY);
}

这些功能构成了拖放和动画功能的基础。 例如,我们使用 Bounds.intersects()确定瓷砖是否与游戏板上的空间重叠; 我们用 Point.vector()确定拖动的方块的方向; 我们使用 Point.interpolate()与计时器结合使用可创建补间动画或缓动效果。

顺其自然

虽然在 Flash 中更容易生成固定大小的布局,但使用 HTML 和 CSS 盒模型更容易生成流体布局。 考虑以下具有可变宽度和高度的网格视图:

此布局没有固定尺寸:缩略图从左到右、从上到下排列。

或者考虑聊天面板。 Flash 版本需要多个事件处理程序来响应鼠标操作、可滚动区域的掩码、计算滚动位置的数学以及许多其他代码将它们粘合在一起。

Flash 中的聊天面板漂亮但复杂。

相比之下,HTML 版本只是一个 <div>具有固定高度和溢出属性设置为隐藏。 滚动不花我们任何钱。

工作中的 CSS 盒子模型。

在这种情况下——普通的布局任务——HTML 和 CSS 胜过 Flash。

你能听到我吗?

我们挣扎于 <audio>标签——它根本无法在某些浏览器中重复播放短音效。 我们尝试了两种解决方法。 首先,我们用死气填充声音文件,使它们更长。 然后我们尝试跨多个音频通道交替播放。 这两种技术都不是完全有效或优雅的。

最终我们决定推出我们自己的 Flash 音频播放器并使用 HTML5 音频作为后备。 这是 Flash 中的基本代码:

var sounds = new Array();

function playSound(path:String):void {
  var sound:Sound = sounds[path];

  if (sound == null) {
    sound = new Sound();
    sound.addEventListener(Event.COMPLETE, function (evt:Event) {
      sound.play();
    });
    sound.load(new URLRequest(path));
    sounds[path] = sound;
  }
  else {
    sound.play();
  }
}

ExternalInterface.addCallback("playSound", playSound);

在 JavaScript 中,我们尝试检测嵌入式 Flash 播放器。 如果失败,我们创建一个 <audio> 每个声音文件的节点:

function play(String soundId) {
  var src = "/audio/" + soundId + ".mp3";

  // Flash
  try {
    var swf = window["swfplayer"] || document["swfplayer"];
    swf.playSound(src);
  }
  // or HTML5 audio
  catch (e) {
    var sound = document.getElementById(soundId);
    if (sound == null || sound == undefined) {
      var sound = document.createElement("audio");
      sound.id = soundId;
      sound.src = src;
      document.body.appendChild(sound);
    }
    sound.play();
  }
}

请注意,这仅适用于 MP3 文件——我们从来没有费心去支持 OGG。 我们希望在不久的将来,该行业将采用单一格式。

轮询位置

我们在 HTML5 中使用与在 Flash 中相同的技术来刷新游戏状态:每 10 秒,客户端向服务器请求更新。 如果自上次轮询以来游戏状态发生了变化,则客户端接收并处理这些变化; 否则,什么也不会发生。 这种传统的轮询技术是可以接受的,即使不是很优雅。 我们希望切换到 长轮询 WebSockets 但是,随着游戏的成熟和用户开始期望通过网络进行实时交互, 。 尤其是 WebSockets 将提供许多增强游戏玩法的机会。

多么好的工具!

我们使用 Google Web Toolkit (GWT) 来开发前端用户界面和后端控制逻辑(身份验证、验证、持久性等)。 JavaScript 本身是从 Java 源代码编译而来的。 例如,点函数改编自 Point.java:

package com.wordico.client.view.layout;

import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.DomEvent;

public class Point {
  public double x;
  public double y;

  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

  public double distance(Point point) {
    double a = point.x - this.x;
    double b = point.y - this.y;

    return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
  }
  ...
}

某些 UI 类具有相应的模板文件,其中页面元素“绑定”到类成员。 例如, ChatPanel.ui.xml对应于 ChatPanel.java:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">

<ui:UiBinder
  xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:g="urn:import:com.google.gwt.user.client.ui"
  xmlns:w="urn:import:com.wordico.client.view.widget">

  <g:HTMLPanel>
   <div class="palette">
    <g:ScrollPanel ui:field="messagesScroll">
       <g:FlowPanel ui:field="messagesFlow"></g:FlowPanel>
    </g:ScrollPanel>
    <g:TextBox ui:field="chatInput"></g:TextBox>
   </div>
  </g:HTMLPanel>

</ui:UiBinder>

完整的细节超出了本文的范围,但我们鼓励您为下一个 HTML5 项目检查 GWT。

为什么要使用 Java? 首先,对于严格的类型。 虽然动态类型在 JavaScript 中很有用——例如,数组能够保存不同类型的值——但在大型、复杂的项目中可能会让人头疼。 第二,重构能力。 考虑一下如何在数千行代码中更改 JavaScript 方法签名——这并不容易! 但是有了一个好的 Java IDE,它就变得轻而易举了。 最后,出于测试目的。 为 Java 类编写单元测试胜过“保存并刷新”这一历史悠久的技术。

概括

除了我们的音频问题,HTML5 大大超出了我们的预期。 Wordico 不仅看起来和在 Flash 中一样好,而且在每一点上都一样流畅和灵敏。 如果没有 Canvas 和 CSS3,我们不可能做到这一点。 我们的下一个挑战:使 Wordico 适合移动使用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

德意的啸

暂无简介

0 文章
0 评论
608 人气
更多

推荐作者

謌踐踏愛綪

文章 0 评论 0

开始看清了

文章 0 评论 0

高速公鹿

文章 0 评论 0

alipaysp_PLnULTzf66

文章 0 评论 0

热情消退

文章 0 评论 0

白色月光

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文