Mario5

分享于 

34分钟阅读

Web开发

  繁體

。用于HTML5的超级马里奥

介绍

在电脑游戏的历史中,一些游戏创造并承载了整个公司。 其中一个游戏当然是 Mario。 首先在游戏 Mario Mario,Donkey在自己的游戏系列中 became,从最初的游戏开始,马里奥兄弟 Bros。 in Mario Mario Mario Mario Mario Mario Mario spin spin spin spin。 在本文中,我们将开发一个非常简单的超级马里奥克隆,它很容易扩展新项目,敌人,heros和当然水平的水平,levels。

游戏本身的代码将用面向对象的JavaScript编写。 现在,这听起来像一个陷阱,因为JavaScript是基于基于脚本的脚本语言,但是有多个对象。 我们将研究一些代码,这些代码将给我们一些面向对象的约束。 这将是非常有用的保持在相同的Pattern 通过整个编码。

背景

Mario5 YouTube

这个应用程序的最初版本是由两个学生在"用 HTML5.CSS3和javascript编程Web应用"上做了我的演讲。 我给了他们一个基本的代码引擎,他们开发了一个游戏,包括水平编辑器,声音和图形。 游戏本身并不包含很多 Bug,但是性能相当差,而且原型属性的使用也有限。 主要性能燃烧器是 Spritely ( 可以在这里找到 ) 中的jQuery插件的使用。 这种情况下,我是罪魁祸首,因为我推荐使用它来简化。 这里的问题是Spritely本身做了一个很好的动画,但不是一百个或者更多。 每个新动画( 即使在同一时刻繁殖) 都会得到它自己的定时间隔。

对于本文,我决定把重点放在游戏的主要内容上。 通过本文,我们将重写整个游戏- 与上面解释的好处一样:

  • 这个游戏很容易扩展。
  • 动画将不会影响对象的动画效果
  • 游戏的开始或者暂停动作将直接影响到所有的元素
  • 游戏将不依赖外部元素,如声音,图形-。

最后的语句听起来像一个写这篇文章的疯子。 然而,在我看来,这是非常重要的。 让我们用下面的代码来说明我的观点:

$(document).ready(function() {
 var sounds = new SoundManager();//**var level = new Level('world');//world is the id of the corresponding DOM container level.setSounds(sounds);//* level.load(definedLevels[0]);
 level.start();
 keys.bind();
});

现在这看起来不那么恶意,但事实上,这实际上所需要的一切都是用HTML5来玩的。 我们在这里留下的细节稍后会解释。 回到 上面的语句中,我们看到一行标有两个星号注释( //** )的行: 这里创建了声音管理器类的一个新实例。 这个也将加载声音效果。 如果我们想跳过这一行? 我们就没有声音管理器的工作实例。 接下来要注意的是,声音管理器的实例没有保存在全局作用域中,而是在本地作用域中。 因为整个游戏中没有对象需要这个类的特定实例,所以我们可以这样做。 如果对象想播放声音,它调用一个由级别( 为了存在,每个对象都必须属于某个级别;因为级别类是创建游戏对象的唯一地方。) 提供的方法。

现在这就是单星评论( //* ) 进入的地方。 如果不调用级别实例的setSounds() 方法,级别将不会有适当的声音管理器类实例。 因此,所有对象播放声音的请求都将被清除。 这使得声音管理器类 plugable,因为我们只需删除两行代码来完全删除声音管理器。 另一方面,我们只需要添加两行代码。 这当然是在 C# 中使用反射( 根据依赖注入或者其他模式所要求的) 实现更加优雅的过程。

剩下的小代码只是加载一个启动级别( 这里我们使用了预先定义级别的List 中的第一个) 并启动它。 名为 keys的全局键盘对象可以 bind() 或者 unbind() 显示文档中的所有键事件。

基本设计

。web浏览器的超级马里奥

我们将跳过本文中的声音管理器实现。 有另外一篇关于好级别编辑器和它的他有趣的事情的文章,它的中一个将是声音管理器的实现。 超级马里奥游戏的基本文档大纲如下所示:

<!doctypehtml><html><head><metacharset=utf-8 /><title>Super Mario HTML5</title><linkhref="Content/style.css"rel="stylesheet"/></head><body><divid="game"><divid="world"></div><divid="coinNumber"class="gauge">0</div><divid="coin"class="gaugeSprite"></div><divid="liveNumber"class="gauge">0</div><divid="live"class="gaugeSprite"></div></div><scriptsrc="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script><scriptsrc="Scripts/testlevels.js"></script><scriptsrc="Scripts/oop.js"></script><scriptsrc="Scripts/keys.js"></script><scriptsrc="Scripts/sounds.js"></script><scriptsrc="Scripts/constants.js"></script><scriptsrc="Scripts/main.js"></script></body></html>

所以我们在这里没有多少标记。 我们看到这个世界包含在一个游戏区域里。 游戏区域包括 gauges ( 和一些精灵来制作仪表的动画)。 这个简单的mario游戏只包含两个量规: 一个用于硬币另一个用于生活的数量。

一个真正重要的部分是将包含的javascript的List。 出于性能原因,我们将它们放在页面的底部。 这也是我们从 CDN ( 在这个案例中哪个是谷歌) 中获取jQuery的原因。 其他脚本应该打包成一个并最小化( 这称为捆绑,是 ASP.NET MVC 4所包含的特性之一)。 我们不会为本文捆绑这些脚本。 让我们看一下这些脚本文件的内容:

  • testlevels.js 文件非常大,应该最小化。 它包含预制的等级。 所有这些级别都由我演讲的两个学生实现。 第一级是经典的for ( 如果我在这里没有错)的第一级超级玛丽土地的克隆。
  • oop.js 文件包含了简化面向对象的JavaScript的代码。 我们将在一分钟内讨论。
  • keys.js 文件创建 keys 对象。 如果我们想要设计多人或者它的他特性,我们也应该modulize这个脚本( 就像我们对sound类的操作一样)。
  • 声音管理器是在 sounds.js 文件中编写的。 它的基本思想是,网络音频 API ( 官方网站。) 可能会超越当前的API。 现在问题是网络音频API只限于 Google Chrome 和一些夜间构建 Safari。 如果发布是正确的,这可能是我们在浏览器中获取游戏所需的音频的方法。
  • 文件 constants.js 包含枚举和非常基本的helper 方法。
  • 所有其他对象都捆绑在文件 main.js 中。

我们先看一下 CSS file:,然后再详细了解实现

@font-face {
 font-family: 'SMB';src: local('Super Mario Bros.'),
 url('fonts/Super Mario Bros.ttf') format('truetype');font-style: normal;}
#game {
 height: 480px;width: 640px;position: absolute;left: 50%;top: 50%;margin-left: -321px;margin-top: -241px;border: 1px solid #ccc;overflow: hidden;}
#world { 
 margin: 0;padding: 0;height: 100%;width: 100%;position: absolute;bottom: 0;left: 0;z-index: 0;}
.gauge {
 margin: 0;padding: 0;height: 50px;width: 70px;text-align: right;font-size: 2em;font-weight: bold;position: absolute;top: 17px;right: 52px;z-index: 1000;position: absolute;font-family: 'SMB';}
.gaugeSprite {
 margin: 0;padding: 0;z-index: 1000;position: absolute;}
#coinNumber {
 left: 0;}
#liveNumber {
 right: 52px;}
#coin {
 height: 32px;width: 32px;background-image: url(mario-objects.png);background-position: 0 0;top: 15px;left: 70px;}
#live {
 height: 40px;width: 40px;background-image: url(mario-sprites.png);background-position: 0 -430px;top: 12px;right: 8px;}
.figure {
 margin: 0;padding: 0;z-index: 99;position: absolute;}
.matter {
 margin: 0;padding: 0;z-index: 95;position: absolute;width: 32px;height: 32px;}

现在又不是( 但我们的标记也不多) 了。 在上面我们介绍一些漂亮的字体,以便给我们的游戏一个Mario看起来像( 键入)。 然后我们将所有的特性设置为 640 x 480像素分辨率。 这是非常重要的游戏确实隐藏任何 overflow。 所以我们可以在游戏中移动我们的世界。 这意味着游戏就像一个。 级别本身被放置在世界上。 仪表放在视图的第一行。 每个图形都有一个名为 figure的CSS类。 对于地面,装饰元素或者物品也一样: 这些元素有一个名为 matter的CSS类。 属性 z-index 非常重要。 我们总是希望一个动态对象位于 static 一个( 有例外,但稍后我们会来)的前面。

面向对象的JavaScript

使用面向对象的代码进行面向对象编程并不困难,但是有点混乱。 其中的一个原因是有多种方法来实现它。 每种方法都有自己的优点和缺点。 对于这个游戏,我们要严格遵循一个 Pattern。 因此,我建议采用以下方法:

var reflection = {};
(function(){
 var initializing = false, fnTest =/xyz/.test(function(){xyz;})?/b_superb/:/.*/;
 // The base Class implementation (does nothing)this.Class = function(){ };
 // Create a new Class that inherits from this class Class.extend = function(prop, ref_name) {
 if(ref_name)
 reflection[ref_name] = Class;
 var _super = this.prototype;
 // Instantiate a base class (but only create the instance,// don't run the init constructor) initializing = true;
 var prototype = newthis();
 initializing = false;
 // Copy the properties over onto the new prototypefor (var name in prop) {
 // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && 
 typeof _super[name] == "function" && fnTest.test(prop[name])?
 (function(name, fn) {
 returnfunction() {
 var tmp = this._super;
 // Add a new. _super() method that is the same method// but on the super-classthis._super = _super[name];
 // The method only need to be bound temporarily, so we// remove it when we're done executingvar ret = fn.apply(this, arguments); 
 this._super = tmp;
 return ret;
 };
 })(name, prop[name]) :
 prop[name];
 }
 // The dummy class constructorfunction Class() {
 // All construction is actually done in the init methodif (!initializing && this.init )
 this.init.apply(this, arguments);
 }
 // Populate our constructed prototype object Class.prototype = prototype;
 // Enforce the constructor to be what we expect Class.prototype.constructor = Class;
 // And make this class extendable Class.extend = arguments.callee;
 return Class;
 };
})();

这里代码受到Prototype的高度启发,并由 John Resig构建。 他在博客( 文章中的文章,关于面向对象的JavaScript代码。) 上写了一篇关于整个编码问题的文章。 代码被包装在一个直接执行的匿名方法中,以便范围内变量。 Class 对象是 window 对象( 它应该是基础对象 换句话说,如果这里脚本文件从网络浏览器中执行,则为 this )的扩展。

我对这个代码的扩展是可以对类进行 NAME。 JavaScript没有强大的反射属性,这就是为什么我们需要对类进程进行描述的原因。 将构造函数赋给变量( 稍后我们会看到) 时,我们可以选择将类的NAME 作为第二个参数传递。 如果这样做的话,那么对构造函数的引用将被放置在对象 reflection 中,第二个参数作为属性名。

一个简单的类构造如下:

var TopGrass = Ground.extend({
 init: function(x, y, level) {
 var blocking = ground_blocking.top;
 this._super(x, y, blocking, level);
 this.setImage(images.objects, 888, 404);
 },
}, 'grass_top');

这里我们创建了一个名为 TopGrass的类,它继承自 Ground 类。 init() 方法表示类的构造函数。 为了调用基构造函数( 不需要),我们必须通过 this._super() 方法调用它。 这是一个特殊方法,可以在任何重写方法中调用。

这里有重要评论: 在这里区分实际多态( 这可以用面向对象的语言来完成,如) 和这里给出的内容是很重要的。 显然,从( 因为我们不能改变我们看到对象的方式- 它总是一个动态对象) 访问父对象的方法是不可能的。 因此访问父方法的唯一方法是调用对应的重写方法中的this._super() 方法。 但也应该注意,这个语句不是绝对的,但是它是以上面所示的代码为 true的。

Ground 类是非常无趣的( 只是一个中间层)。 下面让我们看一下 Ground的基类,称为 matter:

var Matter = Base.extend({
 init: function(x, y, blocking, level) {
 this.blocking = blocking;
 this.view = $(DIV).addClass(CLS_MATTER).appendTo(level.world);
 this.level = level;
 this._super(x, y);
 this.setSize(32, 32);
 this.addToGrid(level);
 },
 addToGrid: function(level) {
 level.obstacles[this.x/32][this.level.getGridHeight() - 1 - this.y/32] = this;
 },
 setImage: function(img, x, y) {
 this.view.css({
 backgroundImage : img? c2u(img) : 'none',
 backgroundPosition : '-' + (x || 0) + 'px -' + (y || 0) + 'px',
 });
 this._super(img, x, y);
 },
 setPosition: function(x, y) {
 this.view.css({
 left: x,
 bottom: y
 });
 this._super(x, y);
 },
});

这里我们扩展 Base 类( 那是最顶级的)。 从 matter 继承的所有类都是 static 32 x 32像素块( 他们不能动),包含一个阻塞变量( 虽然它可以被设置为无阻塞,但 比如的装饰是如此)。 因为每个 matterfigure 实例都表示一个可视对象,所以我们需要为它创建一个适当的视图。 这也解释了为什么 setImage() 方法被扩展,以便在视图中设置图像。

同样的原因也可以用于重写 setPosition() 方法。 为了给子类提供将创建的实例添加到给定级别的obstacles array的标准行为,已经添加 addToGrid 方法。

控制游戏

游戏基本上由键盘控制。 因此我们需要将相应的事件处理程序绑定到文档。 我们只对一些按键感兴趣,可以按下或者释放。 因为我们需要监视每个键的状态,所以我们只使用相应的属性(。对于左键 leftright 为右键等) 来扩展对象 keys。 要想把键绑定到文档或者 unbind(),我们需要调用 bind() 来释放它们。 我们再次使用jQuery为我们做实际的工作- 给我们更多的时间,而不是任何交叉浏览器问题。

var keys = {
 //Method to activate binding bind : function() {
 $(document).on('keydown', function(event) { 
 return keys.handler(event, true);
 });
 $(document).on('keyup', function(event) { 
 return keys.handler(event, false);
 });
 },
 //Method to reset the current key states reset : function() {
 keys.left = false;
 keys.right = false;
 keys.accelerate = false;
 keys.up = false;
 keys.down = false;
 },
 //Method to delete the binding unbind : function() {
 $(document).off('keydown');
 $(document).off('keyup');
 },
 //Actual handler - is called indirectly with some status handler : function(event, status) {
 switch(event.keyCode) {
 case57392://CTRL on MACcase17://CTRLcase65://A keys.accelerate = status;
 break;
 case40://DOWN ARROW keys.down = status;
 break;
 case39://RIGHT ARROW keys.right = status;
 break;
 case37://LEFT ARROW keys.left = status; 
 break;
 case38://UP ARROW keys.up = status;
 break;
 default:
 returntrue;
 }
 event.preventDefault();
 returnfalse;
 },
 //Here we have our interesting keys accelerate : false,
 left : false,
 up : false,
 right : false,
 down : false,
};

另一种设置这个方法的方法是使用jQuery的强大功能。 我们可以将相同的方法分配给上下键事件,使用一些自定义参数。 这个参数将决定哪个事件负责方法调用。 然而,这种方法更简洁,更易于理解。

CSS Spritesheets

游戏中的所有图形都是使用 CSS spritesheets完成的。 如果我们知道以下行,那么这个特性就很简单了。 首先,为了使用图像作为 spritesheet,我们需要将图像作为 background 图像分配给相应的元素。 不要禁用 background 重复,因为这将给我们带来周期性边界条件的优势。 通常这不会导致任何坏的结果,但是,周期边界条件我们可以做更多的事情。

接下来的步骤是设置一种我们刚刚指定的background 图像偏移量。 通常偏移量只是( 0,0 )。 这意味着我们的元素左上坐标也是我们spritesheet的左上坐标。 在这个例子中,换句话说,是相对于元素的偏移,它设置了( 20,10 )的左上( 0,0 ) 坐标,将spritesheet的左上角设置为 20像素到元素的顶部。 如果我们使用( -20,-10 ),我们将会对元素外部spritesheet的左上角的( 0,0 ) 坐标有影响。 因此图像的可见部分将在图像( 而不是在边界上) 中。

spritesheet元素的处理

这说明spritesheets在CSS中的工作方式。 我们只需要我们的坐标我们就可以。 整体上我们可以区分同类spritesheets和异构 spritesheets。 第一个确实有固定网格( 比如。 每个元素 32px 次 32px,导致容易偏移计算,后一个没有固定网格。 图显示了一个异构的spritesheet。 为了通过减少HTTP请求来提高性能,我们为主页创建 spritesheet,我们通常会得到一个异构的spritesheet。

对于动画,我们应该坚持到均匀的spritesheets,以减少动画本身所需的信息量。 游戏使用下面的代码来执行spritesheet动画:

var Base = Class.extend({
 init: function(x, y) {
 this.setPosition(x || 0, y || 0);
 this.clearFrames();
 },
 /* more basic methods like setPosition(),.. . */ setupFrames: function(fps, frames, rewind, id) {
 if(id) {
 if(this.frameID === id)
 returntrue;
 this.frameID = id;
 }
 this.frameCount = 0;
 this.currentFrame = 0;
 this.frameTick = frames? (1000/fps/constants.interval) : 0;
 this.frames = frames;
 this.rewindFrames = rewind;
 returnfalse;
 },
 clearFrames: function() {
 this.frameID = undefined;
 this.frames = 0;
 this.currentFrame = 0;
 this.frameTick = 0;
 },
 playFrame: function() {
 if(this.frameTick && this.view) {
 this.frameCount++;
 if(this.frameCount> = this.frameTick) { 
 this.frameCount = 0;
 if(this.currentFrame === this.frames)
 this.currentFrame = 0;
 var $el = this.view;
 $el.css('background-position', '-' + (this.image.x + this.width * 
 ((this.rewindFrames? this.frames - 1 : 0) - this.currentFrame)) + 
 'px -' + this.image.y + 'px');
 this.currentFrame++;
 }
 }
 },
});

因为我们将spritesheet功能包括在最基本的( 游戏) 类中,因为每个更特殊的类( 如图形或者项) 都。 这保证了spritesheet动画的可用性。 基本上每个对象都有一个函数来设置spritesheet动画称为 setupFrames()。 需要指定的参数是每秒( fps )的帧数和 spritesheet ( frames ) 中的帧数。 动画通常从左到右执行- 所以我们包括参数 rewind 来改变动画从右到左。

这里方法的一个重要内容是可选的id 参数。 在这里我们可以指定一个可以识别当前动画的值。 然后可以使用这种方法来区分将要设置的动画是否已经运行。 如果是这种情况,我们将不会重置 frameCount 和其他内部变量。 如何在某些类中使用这里代码? 让我们以 Mario 类本身的示例为例:

var Mario = Hero.extend({
 /*...*/ setVelocity: function(vx, vy) {
 if(this.crouching) {
 vx = 0;
 this.crouch();
 } else {
 if(this.onground && vx> 0)
 this.walkRight();
 elseif(this.onground && vx <0)
 this.walkLeft();
 elsethis.stand();
 }
 this._super(vx, vy);
 },
 walkRight: function() {
 if(this.state === size_states.small) {
 if(!this.setupFrames(8, 2, true, 'WalkRightSmall'))
 this.setImage(images.sprites, 0, 0);
 } else {
 if(!this.setupFrames(9, 2, true, 'WalkRightBig'))
 this.setImage(images.sprites, 0, 243);
 }
 },
 walkLeft: function() {
 if(this.state === size_states.small) {
 if(!this.setupFrames(8, 2, false, 'WalkLeftSmall'))
 this.setImage(images.sprites, 81, 81);
 } else {
 if(!this.setupFrames(9, 2, false, 'WalkLeftBig'))
 this.setImage(images.sprites, 81, 162);
 }
 },
 /*.. . */});

在这里我们重写 setVelocity() 方法。 根据当前的状态,我们执行 walkRight() 或者 walkLeft() 等相应的函数。 然后,函数查看当前状态以决定要应用哪个动画。 在这里,我们将可选的id 参数。 我们只在可以应用新动画时更改当前spritesheet位置。 否则,当前动画似乎仍然有效,导致了有效的spritesheet位置。

类关系图

重写整个游戏的目的之一是激励以面向对象的方式描述一切。 这将使编码更有趣,更简单。 此外,final 游戏将包含 LESS Bug。 在创建游戏之前,计划了以下类图:

Class diagram of the game

游戏的结构是为了展示关系和依赖关系。 这种结构的好处之一是能够扩展游戏的能力。 我们将在下一节中研究扩展过程。

继承仅仅是编写面向对象的JavaScript的因素之一。 另一种能力是具有类型(。我们的类的实例) 之类的功能。 例如我们可以问一个对象是否是某个类的实例。 让我们来看看下面的代码示例:

var Item = Matter.extend({
 /* Constructor and methods */ bounce: function() {
 this.isBouncing = true;
 for(var i = this.level.figures.length; i--; ) {
 var fig = this.level.figures[i];
 if(fig.y === this.y + 32 && fig.x> = this.x - 16 && fig.x <= this.x + 16) {
 if(fig instanceof ItemFigure)
 fig.setVelocity(fig.vx, constants.bounce);
 elseif(fig instanceof Enemy)
 fig.die();
 }
 }
 },
})

代码Fragment显示了 Item 类的一部分。 这个类包含一个新方法 bounce(),它让我们可以通过将 isBouncing 属性设置为 true 来上下移动。 就像在原来的马里奥游戏中,你可以杀死那些unluckily站在跳跃项目上的敌人。 另一个流行的例子是,在方向( 如果在弹跳物品上方) give ItemFigure ( 就像蘑菇)的附加动力。

扩展游戏

一个可以一直被包含的东西是新的子画面( 图像) 和动作。 一个例子是在火灾模式下适合 Mario。 这个演示使用的图像与大马里奥相同。 下图显示了适合Mario的火焰:

马里奥显示胜利标志

游戏本身有几个扩展点。 一个明显的扩展点是构建一个新的类并给它一个适当的反射 NAME。 级别可以使用这里 NAME,这将导致级别创建这里类的实例。 我们从一个简单的例子开始实施新的装饰: 左悬挂衬套 !

var LeftBush = Decoration.extend({
 init: function(x, y, level) {
 this._super(x, y, level);
 this.setImage(images.objects, 178, 928);
 },
}, 'bush_left');

这相当简单。我们只需从 Decoration 类中获取 inherit,然后通过 setImage() 方法设置另一个。 由于装饰是非块,我们不能在这里指定块级别。 我们将这个新的装饰类。

现在让我们考虑用一个新的敌人扩展游戏的情况: 幽灵( 不包含在源代码中) ! 这有点难,但从原理上看。 这些问题就是这种特定类型的敌人必须遵循的规则。 基本的结构是笔直的:

var Ghost = Enemy.extend(
 init: function(x, y, level) {
 this._super(x, y, level);
 this.setSize(32, 32);
 },
 die: function() {
 //Do nothing here! },
});

首先要注意的是,我们不在 die() 方法中调用 _super() 方法。 这导致 Ghost 无法在( 他或者她已经死了) 中死亡。 这实际上是一个规则。 其他规则是:

  • 鬼魂向马里奥移动( 一旦它能看到马里奥)
  • 如果马克斯直接看到鬼鬼的虚拟( 马里奥的方向与方向相反。),那么鬼鬼就不会移动了
  • 即使马里奥有一颗星或者射也不能死

其他例程滥用 setVelocity() 方法时,重写 move() 方法是很有帮助的。 有以下两个原因:

  • 重力对鬼魂没有任何影响
  • 如果某些规则( 请参见上方) 完成,那么它就会移动

使用这里知识,我们现在可以包含其他幽灵敌人,从而产生以下代码:

var Ghost = Enemy.extend({
 init: function(x, y, level) {
 this._super(x, y, level);
 this.setSize(33, 32);
 this.setMode(ghost_mode.sleep, directions.left);
 },
 die: function() {
 //Do nothing here! },
 setMode: function(mode, direction) {
 if(this.mode!== mode || this.direction!== direction) {
 this.mode = mode;
 this.direction = direction;
 this.setImage(images.ghost, 33 * (mode + direction - 1), 0);
 }
 },
 getMario: function() {
 for(var i = this.level.figures.length; i--; )
 if(this.level.figures[i] instanceof Mario)
 returnthis.level.figures[i];
 },
 move: function() {
 var mario = this.getMario();
 if(mario && Math.abs(this.x - mario.x) <= 800) {
 var dx = Math.sign(mario.x - this.x);
 var dy = Math.sign(mario.y - this.y) * 0.5;
 var direction = dx? dx + 2 : this.direction;
 var mode = mario.direction === direction? ghost_mode.awake : ghost_mode.sleep;
 this.setMode(mode, direction);
 if(mode) 
 this.setPosition(this.x + dx, this.y + dy);
 } else 
 this.setMode(ghost_mode.sleep, this.direction);
 },
 hit: function(opponent) { 
 if(opponent instanceof Mario) {
 opponent.hurt(this);
 }
 },
}, 'ghost');

我们所有的规则都被应用。 ghost只在Mario范围内(。这里案例中的800像素)。 为了协调工作,我们引入了一个名为 ghost_mode的新枚举对象:

var ghost_mode = {
 sleep : 0,
 awake : 1,
};

我们还需要引入一些新的子画面。 在本例中,我们刚刚添加了一个包含所有子画面的新图像。 路径保存在 images.ghost 中,并导致以下图像:

Mario Ghost Sprites

Points of Interest

这篇文章被称为HTML5的超级 soa,但是这个演示中没有真正的HTML5. HTML5提供的两个特性是 <canvas> 元素和 <audio> 元素。 这两者都没有用于这个演示。 对于级别编辑器( 我们将在下一篇文章中介绍) 来说,第一个是有趣的,后者当然已经有趣了。 为了使源代码的大小尽可能小,我决定在这个演示中排除它。

尽管JavaScript是一种动态语言,我们仍然可以使用像变量一样的标志枚举。 例如阻塞变量在标志枚举中定义,如 e.g.:

var ground_blocking = {
 none : 0,
 left : 1,
 top : 2,
 right : 4,
 bottom : 8,
 all : 15,
};

包含 ground_blocking 值的变量,使用 var blocking = ground_blocking.left + ground_blocking.top; ( 可以能也可以通过一个位操作来实现,但是由于JavaScript编写的语句没有任何好处)。 读取原子值的过程可以通过以下方法来完成:

//e.g. check for top-blockingfunction checkTopBlocking(blocking) {
 if((ground_blocking.top & blocking) === ground_blocking.top)
 returntrue;
 returnfalse;
}

原始版本( 包括声音和代码) 可以在 http://www.florian-rappl.de/html5/projects/SuperMario/ 找到。 这个版本不久也会在线。

历史记录

  • v1.0.0 | 初始发行版|。
  • 收费为 |,修复了一些错误,包括YouTube视频,延长了ghost代码| 1 05.06.2012.

相关文章