SpaceShoot单个播放机

分享于 

32分钟阅读

Web开发

  繁體

操作中的SpaceShoot Singleplayer

介绍

在最初的SpaceShoot文章中,展示了一个完整的HTML5游戏的蓝图。 这一次我们实现了许多以前用于扩展游戏的想法。 本文将介绍游戏本身及其代码部分。 我们将浏览以下主题:

  • 在HTML中构建一个菜单并通过JavaScript控制它
  • 使用 LocalStorage 保存设置和个人高分
  • 编码管理器类型,使加载过程易于扩展
  • 集成级别使游戏变得有趣
  • 构建炸弹( 备用武器)
  • 实现CPU控制的船舶
  • 使用文本使游戏内容更加丰富
  • 一种文本控制的文本控件的构建

在我们触摸这些主题之前,我们应该看看游戏的当前状态。

背景

本文是 SpaceShoot的后续文章,在HTML5中是一个多人游戏,可以在这里找到 整个项目起源于使用 WebSocket 元素构建东西的想法,该项目最近已经被引入。 然而,很明显,一个全功能的单一玩家模式也会非常有用。

将有另一篇关于多人操作的完整实现的文章。 这个主要包含 C#,因为服务器是用 Fleck的帮助写的,由 Jason Staten编写。 finally 我还在考虑一个移动版本。 这个版本可以是 Windows Phone 7上的多人版本,或者只是在 html/css中使用响应式设计技术。

游戏本身

SpaceShoot的概念非常简单: 玩家控制着漂浮在太空中的宇宙飞船,被小行星和其他船只攻击。 游戏通过使用带有以下按键的键盘来控制:

可以从主菜单中到达的键盘子菜单

  • 加速( ),断开( 向下磅),以及左( 左 ) 和右( 右磅)的箭头键
  • 射击正常弹药的空间
  • 用于部署炸弹的 Ctrl Ctrl
  • 得分屏幕可以使用标签键打开和关闭
  • 菜单可以通过使用 Esc键来到达和离开

玩家以全生命和全弹药开始,但没有炸弹和盾牌。 为了补充,补充弹药,获取炸弹,或者打开屏蔽,玩家必须收集偶尔提供的项目。 这些物品在它们的存在中受到限制 换句话说,在一定时间后消失。 这是由一个退化的进度栏 below 显示的。 可以收集以下项目:

  • 健康再生 - 提供 +30个运行状况
  • 弹药包 - 提供 +20弹药
  • 附加炸弹 - 提供 +2个炸弹
  • 护盾开关 - 完全屏蔽

收集物品将给玩家提供点数。 同时,一定要知道即使某些包可能增加某种属性,比如,健康,弹药或者炸弹计数,属性仍然保持某些限制,属性的值不能超过它的最大值。 比如,健康,给我们一个从 0 ( 死键) 到 100 ( 最大运行状况)的范围。 在 87健康状态下收集健康包( +30健康) 将导致 100健康,而不是 117.

防护盾是保护飞船的特殊能力。 防护罩会保护飞船的安全。 它的出现将随着级别数的增加而增加。 这很重要,因为小行星计数也会随着级别数的增加而增加。 另外,计算机控制的无人机( 它们是定期发出的) 在更高级别上将会更加痛苦。 每 15th 位,一个完整的无人机发射出去,以摧毁玩家的飞船。 那些armadas与水平计数成比例。

计算机控制船试图直接在玩家飞船的direction 中飞行。 如果玩家足够接近,他们会按一定的间隔开火。 如果小行星在他们的方向上也会激发。 然而,为了避免小行星,它们不会试图 make。

代码

代码没有更改( 架构架构),自上次。 主要区别是代码在很多方面被扩展了。 值得注意的是,我上次提到的一些改进已经包含在这个版本中。 本节将对所有主要扩展和改进进行深入的讨论。

菜单实现

操作中的菜单

菜单是使用DOM实现的。 这里我们要使用 HTML,而不是仅仅滥用 Canvas 元素( 毕竟,CSS给出的样式可以能不应该被避免,尽可以能多地使用。)。 使用jQuery会给我们另一个生产提升。 然而,在这种情况下,为了开发菜单而不使用任何外部库,jQuery被排除了。 基本HTML的生成方式与下面的代码类似:

<divid="menuscreen"><pid="title">SpaceShoot</p><ulid="menu-main">...</ul><ulid="menu-scr">...</ul>...</div>

所以我们生成一个包含所有菜单的<div> -element。 在CSS中,我们应用一些精彩的样式来保持一切美观和谐。 将使用 <ul> -lists显示将显示的不同菜单项。 让我们了解一下如何在我们的JavaScript代码中应用这些事件:

menu.init = function() {
 document.getElementById('menu-sp').onclick = function() {
 game.startSingle();
 menu.toggle();
 };
 document.getElementById('menu-setname').onclick = function() {
 menu.select('menu-user');
 };
 // Others.. .};

所有菜单函数都放在一个名为 menu的对象中。 init() 方法将设置菜单 换句话说,设置右菜单可见并设置所有单击回调以进一步使用。 有些元素有直接点击事件,比如启动单个播放器( 然后关闭菜单),其他元素打开其他子菜单。 洞口由 menu.select(id) 完成。 下面是执行下面的代码:

menu.select = function(id) {
 var m = document.getElementById('menuscreen');
 var items = m.getElementsByTagName('ul');
 for(var i = items.length; i--; ) {
 var isel = items[i].id === id;
 items[i].style.display = isel? 'block' : 'none';
 }
};

方法只打开菜单并获取所有底层子菜单。 所有这些子菜单都将被隐藏,除了应该选择( 显示)的那个子菜单。 使用 jQuery,这样的命令可以写在一行中。 菜单( 显示或者隐藏)的切换是由 toggle() 方法完成的。 这里方法已经按以下方式实现:

menu.toggle = function() {
 var m = document.getElementById('menuscreen');
 var im = document.getElementById('inactive')
 if(m.style.display === 'block') {
 game.running = true;
 m.style.display = 'none';
 im.style.display = 'none';
 c.canvas.focus();
 } else {
 game.running = game.running && game.multiplayer;
 m.style.display = 'block';
 im.style.display = 'block';
 menu.select('menu-main');
 m.focus();
 } 
};

方法检查菜单当前是否显示为( 在菜单屏幕 <div>display 规则上)。 然后,根据可见性状态执行正确的语句。 带有 ID inactive的<div> -element只是在菜单和文档/正文之间放置的一个层。 因这里文档的它的余部分将有一个查看,指示焦点现在在菜单上,菜单必须关闭以继续。

在菜单上很容易设置一些选项

使用CSS样式,很容易调整菜单中文本框和标题的设计。 由于我们的JavaScript设计是用( 或多或少) 唯一事件调用每个按钮,我们可以设置一个事件来捕获本地设置。 所有设置都存储在 LocalStorage 对象中。 这是一个非常有用的概念,现在将。

使用 LocalStorage

。显示由LocalStorage对象存储的排行榜

为了保存设置,以保存个人高分,我们必须处理 Cookies 或者它的他技术。 使用 Cookies 并不是一个好主意,因为它们不仅有限,而且使用起来也很烦人。 LocalStorage 对象是对 Cookies 遇到的大多数问题的解决方案。 为了限制对对象( 因此,限制JSON转换)的访问,我们缓冲当前设置以及当前局部变量中的当前高分。

加载应用程序时,前面的高分数将使用 LocalStorage 对象和JSON加载到本地 array 中。 不幸的是,我们只能在存储中保存类型为 DOMString的变量。 但是,这意味着通过使用 JSON,我们可以存储所有的JavaScript对象。 加载序列是在代码中定义的,如下所示:

score.init = function() {
 var s = JSON.parse(localStorage.getItem('highscores')); 
 if(s)
 score.scores = s;
};

这并不是很复杂。 我们从 LocalStorage 获得保存的高分,如果以前的高分保存( 这肯定不是第一次加载时的情况),则将它的保存在本地缓冲区中。 否则,score.scores 仍然只是一个空的array,因为局部变量 s 是未定义的,因此 false

当前得分屏幕可以在游戏中打开或者关闭,方法是按下标签。 一旦游戏结束,得分屏幕就会自动显示。 下面是一个score屏幕( 所有可能的统计数据)的例子。

当前的得分可以在任何时候在游戏中查看

为了在游戏后更新高分 List,调用 score.update()。 这个将检查当前的分数是否为新的高分。 如果是这种情况,将显示一个模态消息对话框。 在任何情况下,当前分数都被添加到分数的List。 这里外,LocalStorage 对象随当前分数的List 更新,以保持分数最新。

score.update = function() {
 var dd = new Date();
 var pts = myship.points;
 if(pts> score.high().points) {
 //Show Message! }
 score.scores.push({ date : dd, points : pts, level: game.level, player : settings.playerName });
 localStorage.setItem('highscores', JSON.stringify(score.scores));
};

图像和音频管理器

如前一篇文章所述,我们需要一个更强大的管理器来。 由于游戏在某些场景中需要音频,所以还需要另外一个音频对象管理器。 这个 final 单播放器代码包含一个对象 resourceManager,它创建了 imageManageraudioManager 实例来处理子画面。徽标和声音 Fragment。 资源管理器对象的声明方式如下:

var resourceManager = newfunction() {
 this.sounds = [
 //Name of sound files in audio directory without. wav ];
 this.sprites = [
 //Name of image files in img directory without. png ];
 this.done = 0;
 this.total = this.sounds.length + this.sprites.length;
 infoTexts.push(new infoText(c.canvas.width/2, c.canvas.height/2, 
 100, "Loading 0%", secondaryColors[0]));
 draw();
};

我们只需要在不结束的情况下放置文件的NAME,而不需要。 要通知用户加载进程已经启动,信息文本将放置在屏幕的中间。 其他方法是通过执行 init() 方法完成的:

resourceManager.init = function() {
 soundEffects = new soundManager(resourceManager.sounds, resourceManager.callback);
 spriteSheets = new imageManager(resourceManager.sprites, resourceManager.callback); 
};

所以基本上所有的经理都在那里。 首先要注意的是,我们不仅将 array 传递给了对象,而且还传递了回调方法。 方法将处理所有的魔术,确定是否加载所有图像,并将一些有用的输出写入屏幕,以便通知用户。

resourceManager.callback = function() {
 resourceManager.done += 1;
 infoTexts.splice(0, 1);
 if(ready = resourceManager.done === resourceManager.total)
 resourceManager.ready();
 else infoTexts.push(new infoText(c.canvas.width/2, c.canvas.height/2, 100, 
 "Loading" + parseInt(100 * resourceManager.done/resourceManager.total) + "%", 
 secondaryColors[0]));
 draw();
};

在加载所有外部文件( 图像和音频)的情况下,将调用 ready() 方法。 这是将执行关闭任务的。 我们的例子中,最重要的调用是设置网络。 这在当前代码( 单个播放机) 中不会做任何事情。 但是,在第三篇文章中,这将获得一些重要的( 在第一篇文章中,它看起来更像是我们实现了一个非常)。

resourceManager.ready = function() {
 //Set other unimportant tasks network.setup();
};

由于 imageManager 对象的工作原理是上次解释的,因这里这次我们将看到 audioManager。 这个经理和图像中的人有一些主要的区别。 首先,让我们看一下代码:

var soundManager = function(sounds, callback) {
 this.soundNames = sounds;
 this.sounds = [];
 this.count = sounds.length;
 for(var i = 0; i <this.count; i++) {
 var t = document.createElement('audio');
 t.preload = 'auto';
 t.addEventListener('loadeddata', function() {
 callback();
 }, false);
 t.src = 'sounds/' + this.soundNames[i] + '.wav';
 this.sounds.push([t]);
 }
};

构造函数只接受具有声音名称的array,当成功加载声音文件时调用的方法。 为了达到这个目的,我们设置了各种选项。 一个是使用自动预加载。 另一个是将事件监听器绑定到 loadeddata 事件。 这将在加载整个音频Fragment后触发回调方法。 从 imageManager 中知道的load 事件在这里不适用。 最后,声音文件被添加到 array ( <audio> 标签) 中,该由本地管理器对象保存。

接下来我们要看的是 get() 方法。 代码中的所有方法都可以通过 get() 方法访问外部文件( 放置在适当的标签中)。 让我们看看它:

soundManager.prototype.play = function(name) {
 if(settings.playSounds)
 for(var i = this.count; i--; ) { 
 if(this.soundNames[i] === name) {
 var t = this.sounds[i];
 for(var j = t.length; j--; ) { 
 if(t[j].duration === 0)
 return;
 if(t[j].ended)
 t[j].currentTime = 0;
 elseif(t[j].currentTime> 0)
 break;
 t[j].play();
 return;
 }
 var s = document.createElement('audio');
 s.src = t[0].src;
 t.push(s);
 s.play();
 return;
 }
 }
};

那看起来不错为什么我们要用这么多的代码? ! 不是一个简单的this.sounds[i].play()return 语句做这个工作? 答案显然是 ,但原因是: 音频标签( 幸运) 不能播放多次或者混合。 目前,谷歌进行大量编码,为了克服所有这些问题,换句话说,使得HTML标准更适合游戏。 由于大多数实现受到 Google Chrome的限制,因这里所有这些实验仍处于非常早期的阶段。

基本上,代码只是看所有合适的音频标签是否已经在播放 Fragment。 如果是,克隆标记将被创建并添加到 array。 这个新克隆标记将播放声音 Fragment。 如果声音 NAME 与请求的NAME 相等,则标记是匹配的。 图像可以多次使用,因此这个工作只对 audioManager 是必需的。

包括项目和级别

为了让游戏更有趣,有必要给玩家一些自豪的方法: 像级别或者项目(。刷新健康,弹药或者其他)。 所有这些都已经在游戏循环中实现,并在 draw() 方法之前的圆的末尾被调用。 让我们简要看一下要调用的方法:

var items = function() {
 //...if(game.ticks % 200 === 0) {
 //Next Level game.level++;
 //START DRONE WAVE if Level == 15, 30,.. . }
 //Give certain levels (2, 5, 10, 25, 50, 75,.. .) something interestingvar gt = (game.ticks * game.level);
 //Generate an asteroidif(gt % 100 === 0)
 generateAsteroid();
 //Generate an AI controlled droneif(gt % 500 === 0)
 generateDrone();
 //Cool Items!if(game.ticks % 50 === 0) {
 var coin = Math.random();
 if(coin <0.1)
 generateHealthpack();
 elseif(coin <0.2)
 generateAmmopack();
 //... }
};

炸弹包增加了一个很酷的可能性。 当有炸弹包( 由两个炸弹组成的) 时,就可以部署其中一个炸弹。 他们四秒钟后引爆。

更多可能性

放置炸弹并观察引爆是很有趣的

因为只有一个武器只有一个武器才能使用,所以需要包括它的他( 花式) 武器。 在这种情况下,我们选择一个完全不同的: 炸弹的特征很简单 玩家不射击炸弹- 而是他把炸弹。 炸弹不立即爆炸,它给玩家一些时间从爆炸半径爆炸。 这是强制性的,因为炸弹也会伤害玩家。 炸弹的损伤是用一个方形公式计算的。 这是因为炸弹的损坏与它所覆盖的区域成反比。 这个区域是一个圆形,i.e.,我们有一些与弹道半径损伤的方位关系。

为了包括其他武器,需要几个扩展。 一个扩展是另一个项目。 这个实现的方式与弹药包和其他包的实现方式相同。 另一个要求是有一个包含炸弹数量的属性。 如果玩家至少有一个弹炸弹,那么船的逻辑必须查看是否按下了一些键。

设置人工智能

每个无人机都做了几个计算- 首先是) 小行星,然后与播放机"

计算机控制的无人机必须是智能的( 不要担心,这不是天网的时间)。 我们为了节省一些重要的CPU周期,为了使游戏仍然有趣,我们实现了一个非常基本的方法,以保持动作的obtrusive obtrusive obtrusive。 我们的逻辑遵循以下简单条件:

  • 如果小行星直接在飞行路径( 距离一定距离) 中,无人机就必须射击
  • 计算飞行到玩家飞船的角度
  • 如果角度大于某一公差,则开始旋转
  • 如果玩家的船是 below的一定距离,那么射击它

这已经像下面这样实现:

drone.prototype.logic = function() {
 //Set up variables and calculate some tolerancesvar ta = d2g(this.angle); //degrees to grad of current angle//Loop over all asteroidsfor(var i = asteroids.length; i--; ) {
 t1 = asteroids[i].x - this.x;
 t2 = asteroids[i].y - this.y;
 d = Math.sqrt(t1 * t1 + t2 * t2);
 beta = Math.acos((Math.sin(ta) * t1 + Math.cos(ta) * t2)/d);
 //The drone can shoot and it should (angle is OK and distance is OK) shootif(this.cooldown === 0 && beta <tol2 && d <bomb2) {
 this.cooldown = DRONE_INIT_COOLDOWN;
 particles.push(new particle(this.x, this.y, 
 (3 + this.speed) * Math.sin(ta), - (3 + this.speed) * Math.cos(ta), this));
 //LEAVE iterationbreak;
 }
 }
 //Now let's have a look for the player's shipvar f = this.x> ships[0].x? 1 : -1;
 t1 = this.x - ships[0].x;
 t2 = this.y - ships[0].y;
 d = Math.sqrt(t1 * t1 + t2 * t2);
 beta = Math.acos((Math.sin(ta) * f * t1 + Math.cos(ta) * t2)/d);
 //What to do with those numbers?if(beta> tol) {
 this.angle = this.angle + f * ROTATE;
 } elseif(this.cooldown === 0 && d <MAX_BOMB_RADIUS) {
 this.cooldown = DRONE_INIT_COOLDOWN;
 particles.push(new particle(this.x, this.y, 
 (3 + this.speed) * Math.sin(ta), - (3 + this.speed) * Math.cos(ta), this));
 }
 //...};

这是个有趣的开始还有一些愚蠢的结果。 另一方面,无人机将会在颈部(。考虑 150个无人机在级别 150 ) 中造成痛苦,另一方面也会帮助玩家破坏小行星。 这个有趣的副作用也被显示在一个拦截信息文本上,它将显示在所有的无人机。 以后会更多.

褪色文本

。获取健康软件包后的褪色文本

为了以一种不明确和酷的方式显示小信息文本,我们必须引入一个新对象: infoText 基本构造函数非常简单且具有以下源:

var infoText = function(x, y, time, text, color) {
 this.x = x;
 this.y = y;
 this.time = time;
 this.total = time;
 this.text = text;
 this.color = color;
};

基本上,我们只是设置文本的( 中心) 位置以及它所包含的颜色和时间。 为了一次执行减量,我们将时间存储两次,并且仍然具有原始值。 然后使用它来确定颜色的当前alpha状态,其中 1是起始值,0是 final 值。 图形在 draw() 方法中执行:

infoText.prototype.draw = function() {
 c.save();
 c.translate(this.x, this.y);
 c.textAlign = 'center';
 c.fillStyle = 'rgba(' + this.color + ', ' + (this.time/this.total) + ')';
 c.fillText(this.text, 0, 0); 
 c.restore();
};

阻止文本

介绍使用阻止文字以显示精彩故事

淡出文本是一个不错的特性,但它不适合介绍或者信用屏幕或者其他必须 block的逻辑的游戏。 另一个文本的另一点是,一些文本应该慢慢显示,以避免给播放器一个字母。 解决方案是按以下方式设置的introText 对象:

var introText = function(sx, sy, maxwidth, maxheight, lineheight, textArray) {
 this.fulltext = textArray;
 this.currentline = 0;
 this.currentindex = 0;
 this.lines = textArray.length;
 this.linelength = textArray.length> 0? textArray[0].length : 0;
 this.text = [''];
 this.font = '20px Orbitron';
 this.fillcolor = 'rgb(255, 255, 255)';
 this.strokecolor = 'rgb(0, 0, 0)';
 this.x = sx;
 this.y = sy;
 this.lineheight = lineheight;
 this.width = maxwidth;
 this.height = maxheight;
 this.fadetime = 50;
};

在这里我们看到许多属性实际上被设置了。 构造函数调用基本上包括一些要在起始位置( xy ) 显示的文本以及文本的最大宽度和最大高度。 此外,必须指定线路的高度。 重要的是要注意,不同的行必须在文本的array 中分开,它的中每个条目都包含一行文本。

现在画布和 GDI+ ( 包含在. NET 框架中) 中的文本绘图方法不一样方便。 一个缺点是它不自动包含行符或者选项来指定文本的最大宽度。 我们现在唯一的选择是测量文本,并根据文本测量的结果减少一行中的字符数量。

代码将执行以下操作:

  • 当前行是否已经完成? 然后转到下一行。
  • 没有下一行? 然后开始逐渐消失。
  • 计算当前索引并添加下一个单词。
  • 如果从开始到下一个单词的宽度大于直线,则启动新的显示线。
  • 将下一个字母附加到当前显示行。
  • 增加位置索引。

如果将至少一行文本作为参数传递,则代码将工作。 大多数代码可以肯定地使用,而不使用字符显示字符。 基本上,它可以在循环中使用,以便在 <canvas> 元素的框中显示一些文本。

introText.prototype.logic = function() {
 if(this.currentindex === this.linelength) {
 this.currentline += 1;
 this.text.push('');
 this.currentindex = 0;
 if(this.currentline <this.lines)
 this.linelength = this.fulltext[this.currentline].length;
 }
 if(this.currentline === this.lines)
 return --this.fadetime;
 var idx = this.text.length - 1;
 var text = this.text[idx];
 var line = this.fulltext[this.currentline];
 var chr = line[this.currentindex];
 var next = line.indexOf(' ', this.currentindex);
 var plus = '';
 if(next> 0)
 plus = line.substring(this.currentindex, next);
 elseif(next <0)
 plus = line.substring(this.currentindex);
 c.save();
 c.font = this.font;
 if(c.measureText(text + plus).width> this.width)
 this.text.push(chr);
 elsethis.text[idx] = text + chr;
 c.restore();
 this.currentindex += 1;
 returntrue; 
}; 

集成社交连接器

一个全新的特性是主菜单中的社交栏。 请注意,这通常不是一个新的东西: 它只是 SpaceShoot ( 与原始文章比较)的新特性。 这样的连接器可能会帮助任何游戏被知道,因为他们允许人们共享网站。 让我们看看 HTML,包括( 非常基本) 社交插件,Google+和 Twitter:

<divid="promo"><ahref="https://twitter.com/share"class="twitter-share-button"data-url="http://html5.florian-rappl.de/SpaceShootSingle/"data-lang="en"data-count="vertical">Tweet</a><divclass="g-plusone"data-size="tall"data-href="http://html5.florian-rappl.de/SpaceShootSingle/"></div><divclass="fb-like"data-href="http://html5.florian-rappl.de/SpaceShootSingle/"data-send="false"data-layout="box_count"data-width="60"data-show-faces="false"data-action="like"></div><divid="fb-root"></div></div></div>

这是一个应该在网站中正式包括的代码,以使javascript能够工作。 我们只是把这些外部javascript放在一个名为的文件中。 这里文件包含以下代码 Fragment:

!function(d,s,id) {var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)) {
js=d.createElement(s);
js.id=id;
js.src="http://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}}(document,"script","twitter-wjs");
(function(d, s, id) {var js,fjs=d.getElementsByTagName(s)[0];if(d.getElementById(id))return;
js=d.createElement(s);
js.id=id;
js.src="http://connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js,fjs);
}(document, 'script', 'facebook-jssdk'));
(function() {var po=document.createElement('script');
po.type='text/javascript';
po.async=true;
po.src='https://apis.google.com/js/plusone.js';var s=document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();

源代码被稍微修改过。 但是,主要的目的并没有改变。 每个脚本的行为都类似: 它们都创建一个脚本标记并设置适当的目标源。 这里有一个显著的问题是,只有谷歌的脚本执行异步操作( 如果可用的话)。 这是其他脚本中缺少的内容,应该包含在 SpaceShoot ( 或者任何网站)的final 版本中。

Points of interest

为了使游戏与服务器完美地协作,需要对代码进行一些更改。 这些更改将在下一篇文章中详细讨论。 大多数改变意味着自定义生成象小行星。船和它的他物体,以及游戏环节的变化。 现场版还包含了一些社交集成,以便更容易地共享和在单个玩家中进行欺骗。

可以在 http://html5.florian-rappl.de/SpaceShootSingle/ 观看全功能的单人播放器。

个人排行榜

屏幕截图显示我个人的高分。 it应该注意到,这不是最好的high overall我的一位同事 went 251级,得分超过 190000磅。 在如此高级别上,玩家最可能杀死的是不再是小行星的小行星,而是( 波浪) 无人机。 如果没有大量的健康。炸弹。弹药和活动屏蔽,就几乎不可能在 180个无人机上战斗。 祝大家好运 !

这是第二篇基于的SpaceShoot游戏。 第一个可以在CodeProject的CodeProject中查看。

浏览器问题

根据官方的来源,游戏应该能在所有当前浏览器的(。IE 9,Chrome 17,Safari 5.1,Opera 11.6和 Firefox 10 ) 上正常工作。 但是,看起来所有这些浏览器都实现了 <audio> 标记,但并不是所有指定的事件。 因此,你可能会通过使用其中一个浏览器来遇到一些问题。

游戏主要是用 Opera 开发的。 许多测试也在 Google Chrome 上执行- 所以这两个可能是最安全的选项。 如果你在任何浏览器上遇到问题,请立即在评论中报告。

历史记录

  • v1.0.0 | 初始发行版|。
  • 带有社交连接器1的v1.1.0 | 更新。
  • 带有浏览器问题的v1.1.1 | 更新|

play  spa  SIN  播放器  Space  Spaces  
相关文章