我的唠叨:cocos2d-iphone是iOS平台上非常不错的2D游戏引擎,它是基于python版的cocos2d设计的,这里主要说的是它的javascript版。最近打算学习下这个游戏引擎,所以特来翻译下它的相关文档。相关系列译文:
Cocos2d-javascript入门教程一
Cocos2d-javascript入门教程二
Cocos2d-javascript入门教程三
这一系列的教程能够帮助你创建一个新的cocos2d-javascript项目,并且写一个非常简单的入门demo。如果你有任何的问题或建议可以在本文的评论(译者言:当然如果你有什么想和我交流的也可以写到此文的评论中,或发邮件联系我)中写明!
这是教程的第一部分,我们不会写任何的代码,但是我们会创建一个初始项目。并且大致的过一下如何使用模块和类。
下载安装官方网站下载页面中的window安装文件即可。
如果你安装了Node.js和npm,那么你可以通过如下命令安装cocos2d-javascript:
npm install cocos2d
如果你没有安装Node.js和npm,或者你不想装。那么你可以通过下载最新版的ZIP包的方式安装。解压之后,cd到对应目录执行:sudo ./install.sh即可。这个脚本会自动安装好cocos2d。
在Windows上你可以双击快捷方式中的“Create project”,并且设置文件夹的名字为breakout。在Linux和Mac,你可以通过cocos命令来创建项目,命令如下:
cocos new ~/Projects/breakout
现在启动下开法用的web server,来确认项目是否已经被成功创建!(译者言:cocos2d是基于nodejs提供的web server服务!)。在Windows上可以通过双击项目文件夹中的“Server project”快捷方式来启动server。在Linux和Mac上如有打开终端,运行:
cd ~/Projects/breakout cocos server
然后用web浏览器打开地址:http://localhost:4000。如果安装启动成功,那么你会看到一个包含有“Breakout”文字的黑色正方。形。
如果你只为网站写过Javascript,那么Javascript的模块概念可能对你来说比较陌生。Cocos2D的Javascript版实现了CommonJS 模块标准。这就意味着当你想要使用另一个文件(即模块)中的对象时,必需先要import(导入)它。全局的require函数可以import一个模块。你基本上一定会import cocos2d模块,那么你就需要在你的js文件的最上端加入如下的代码:
var cocos = require('cocos2d');
这行代码将所有的cocos模块的对象暴露到cocos变量中。通过这个变量你可以访问cocos模块所有它所公开的对象与方法。这同样能够在浏览器的Javascript console中使用。可以在breakout项目的页面上试一下。在chrome中打开页面并且打开终端。然后输入require('cocos2d');并回车。你就会看到如下的图像了:
Cocos2D-javascript中所有的类都继承自BObject对象。这个对象加入了一系列额外的一般js对象不会有的方法。为了扩展BObject(或其子类)你需要使用它的extend方法,传入一个包含新方法和属性的对象。例如:
var Cat = BObject.extend({ voice: "meow", init: function() { Cat.superclass.init.call(this); }, speak: function() { alert(this.get('voice')); } });
以上代码创建了一个新的Cat类,这个类的对象有一个voice属性和一个speak方法。init方法则是这个类的初始化函数。为了创建一个这个类的实例对象,你需要调用类的create方法。create方法传入的参数将对被init方法使用。所以为了创建一个cat类的实例对象,你可以写如下代码:
var myCat = Cat.create(); myCat.speak();
BObject的子类的属性有一些特殊的能力,例如绑定和getters/setters。为了支持这个,而不是使用Javascript的expando方式(即object.properties的形式)访问属性,你需要使用get和set方法来访问属性。为了访问Cat类的voice属性:
myCat.get('voice');
然后要修改它的voice属性:
myCat.set('voice', 'ROAR!');
没有图像、声音等资源,我们基本上做不了什么好游戏。所以为了导入我们程序中src/resources/文件夹下的资源,src/resources/文件夹下面的如何文件都会被自动编译成Javascript文件。如果你想要控制游戏最终的大小,需要避免放一些无用的文件进去。
为了在代码中访问你的资源文件,你需要使用全局的resource函数。这个可以返回一个对象或字符串包含资源数据。大多数时候你只需要传入一个路径,然后让cocos2D来处理资源。如果你想要访问一个叫src/resources/sprites.png的文件。使用如下代码:
resource('/resources/sprites.png');
它会返回一个HTML的image元素。
应用的入口是src/main.js文件。src/main.js文件中的exports.main函数会在网页被载入(load)的时候调用。你不需要自己去监听load事件,Cocos2D帮你解决一切。
这是cocos2d-javascript基础教程的第二章,整个教程帮助你完成了基于cocos2d-javascript的demo:BreakOut。你至少需要看完第一章之后才能继续。在这一章中我们会创建一个bat(游戏中拍子),然后让它跟随鼠标移动。
首页我们需要一些图像材料。我用自己程序员的艺术细胞制作了一个图片,这个图片是一个sprite(译者言:如果你是前端工程师,对CSS spirite应该很熟悉,就是将多个小图片合并在一起)。它包含了一个拍子、一个球和一些障碍物。将这个图片存到你的project目录下的src/resource文件夹中。
我们的bat类会在一个单独的模块中定义,然后我们会在主模块中import之后使用。
创建一个新的文件叫做src/Bat.js,并且在文件开头import cocos2d和geometry两个模块。
var cocos = require('cocos2d'); var geom = require('geometry');
现在我们会通过扩展cocos的Node类来创建Bat类
var Bat = cocos.nodes.Node.extend({ init: function() { Bat.superclass.init.call(this); } });
这样我们就有一个新的类:Bat。它是cocos.nodes.Node的子类。init方法就是它的构造器,并且第一件事情就是调用父类的构造器。你应该始终都在子类的构造器中调用父类的构造器。bat需要一个图片用于显示,所以我们会添加一个Spirite到Bat类中。
var sprite = cocos.nodes.Sprite.create({ file: '/resources/sprites.png', rect: new geom.Rect(0, 0, 64, 16) });
这里我们传入了一个sprites.png图像的路径,以及一个正方形用于代表这个sprites中的那些部分是我们需要的。你可能已经注意到我们使用的是new操作符来创建对象,而不是create。那是因为geom.Rect不是继承自BOject的,它只是一个普通的Javascript对象。之所以那么做是因为一个像正方形一样简单的功能不需要BOject提供的那么多方法。
Sprite的默认绘制的位置是0 x 0(左上角),默认锚点位置是0.5 x 0.5。这意味着Sprite的中间位置的图像会出现Bat的0 x 0的位置。为了解决这个问题,我们需要改变sprite锚点的位置,这样Spirite的左上角会绘制在Bat的左上角。如下:
sprite.set('anchorPoint', new geom.Point(0, 0));
我们通过给anchorPoint设置一个geometry.Point的对象来实现设置锚点位置的功能。锚点的左上角位置是0 x 0,右下角位置是1 x 1。现在我们的sprite已经准备好添加到Bat节点中了。
this.addChild({child: sprite});
每一个节点都有一个contentSize,这样就可以知道它是多大的。这个值被用来计算位置和边界框。为了达到让Bat节点的大小和我们添加的sprite的大小一样的目的,操作如下:
this.set('contentSize', sprite.get('contentSize'));
Bat类已经完成,为了让其他的模块能够访问到,需要将他暴露出去。这就需要用exports,如下:
exports.Bat = Bat;
回到第一章教程中创建的main.js文件,删掉我们一开始写的绘制text的代码。这些代码是(译者言:这里是原文错误,在我翻译此文时第一章并没有创建这些代码,可以参考Breakout的样例代码来理解:main.js ):
// Get size of canvas var s = cocos.Director.get('sharedDirector').get('winSize'); // Create label var label = cocos.nodes.Label.create({string: 'Breakout', fontName: 'Arial', fontSize: 76}); // Add label to layer this.addChild({child: label, z:1}); // Position the label in the centre of the view label.set('position', geo.ccp(s.width / 2, s.height / 2));
我先来讲解Breakout类。它是继承自cocos.nodes.Layer类。这和其它的Sprite或Bat类一样。但有一个不同的地方,就是Layer是绘制到整个canvas上的,它也会响应鼠标和键盘的事件。在一个程序中应该有且只有一个Layer,你也可以选择有多个Layer,不过事件也会同样被多余的Layer监听。为了访问Bat类,我们需要import Bat.js文件。这就需要调用require方法,并且将得到的值赋值给一个变量。
var Bat = require('Bat').Bat;
现在Bat已经可以使用,让我将它添加到游戏中吧。我们希望游戏中的其他对象也能够访问Bat的对象。所以第一件事情就是在Breakout Layer初始化的时候给它添加一个属性(别忘了逗号)。
bat: null,
下一步我们在构造器(init方法)快结束时创建并赋值bat属性。
var bat = Bat.create(); bat.set('position', new geo.Point(160, 280)); this.addChild({child: bat}); this.set('bat', bat);
我们创建了一个Bat的对象,并且讲它移动到了160 x 280像素的位置。然后将它添加到了Layer中,并且赋值给了bat属性。如果所有的都完成,你可以启动开发用的Web server。在浏览器中看到你的bat。在你的终端中输入如下代码就可以启动Web server(如果是Window则双击“Serve project”):
cocos server
然后用浏览器访问http://localhost:4000。 如果你看不到bat,就检查下Javascript终端的错误消息。如果还是不行,就在这篇教程下面留个评论,我会尽量帮助你。你的Breakout类现在看起来是如下的代码:
var Breakout = cocos.nodes.Layer.extend({ bat: null, init: function() { // You must always call the super class version of init Breackout.superclass.init.call(this); // Add Bat var bat = Bat.create(); bat.set('position', new geo.Point(160, 280)); this.addChild({child: bat}); this.set('bat', bat); } });
如果我们不能控制bat,那这个游戏就不是游戏了。幸运的是我们可以很容易的让bat跟随鼠标运动。我们需要在Breakout layer中启用鼠标。这个可以通过isMouseEnable属性来设置。在init方法中添加:
this.set('isMouseEnabled', true);
当鼠标被启用,layer会有一个回调函数,只要even触发它就会被调用。我们要追踪鼠标位置,就需要重写mouseMoved事件。将如下方法添加到init方法下面,别忘了在“}”之后添加逗号。
mouseMoved: function(evt) { var bat = this.get('bat'); var batPos = bat.get('position'); batPos.x = evt.locationInCanvas.x; bat.set('position', batPos); }
这些代码获取了Bat对象的变量bat,并且改变了它的x轴坐标。重新启动服务,然后就能在浏览器中看到变化了。
var Breakout = cocos.nodes.Layer.extend({ bat: null, init: function() { // You must always call the super class version of init Breakout.superclass.init.call(this); this.set('isMouseEnabled', true); // Add Bat var bat = Bat.create(); bat.set('position', geo.ccp(160, 280)); this.addChild({child: bat}); this.set('bat', bat); }, mouseMoved: function(evt) { var bat = this.get('bat'); var batPos = bat.get('position'); batPos.x = evt.locationInCanvas.x; bat.set('position', batPos); } });
在这一章我们会在游戏中创建一个游戏中的球和它的反弹效果。这里会提到很多的初始化代码,这些代码和Bat(上一章提到的球拍)的代码类似,所以我不会讲太多的细节。
就像球拍一样,球也有一个它自己的模块。需要创建一个叫做Ball.js的文件,并且在最上面import cocos和geometry模块。
var cocos = require('cocos2d'); var geom = require('geometry');
也像球拍一样,球是 cocos.nodes.Nodes的子类Ball。
var Ball = cocos.nodes.Node.extend({ init: function() { Ball.superclass.init.call(this); } });
然后在init方法中添加一个sprite:
var sprite = cocos.nodes.Sprite.create({ file: '/resources/sprites.png', rect: new geom.Rect(64, 0, 16, 16) }); sprite.set('anchorPoint', new geom.Point(0, 0)); this.addChild({child: sprite}); this.set('contentSize', sprite.get('contentSize'));
然后将Ball类暴露出去:
exports.Ball = Ball;
现在回到main.js文件中,并且将Ball类import到这个文件中。
var Ball = require('Ball').Ball;
我们需要另一个变量去赋值Ball对象。
ball: null,
然后在Breakout layer层的init方法中添加Ball:
var ball = Ball.create(); ball.set('position', new geom.Point(160, 250)); this.addChild({child: ball}); this.set('ball', ball);
如果你现在想尝试下这个游戏,你会看到一个不会动的球。
想让球在空中飞行。需要用矢量来表示它的速度,它是geometry.Point的一个对象实例。x和y代表坐标系中x和y轴上的值。
回到Ball.js文件中,添加一个velocity的属性到Ball类中。
velocity: null,
然后在Ball类的init方法中添加velocity的默认值。x和y的值是每秒移动的像素值。
this.set('velocity', new geom.Point(60, 120));
现在我们知道球的运行速度,我们会在它系统的时候更新球的位置。所有的节点会有一个叫scheduleUpdate的方法,这个方法执行一次之后会开始一个定时器,在每一帧绘制时候执行update方法。我们在init方法的最后添加如下代码来启动定时器:
this.scheduleUpdate();
然后创建用来被调用的update方法:
update: function(dt) { }
dt变量表示延迟时间。这是上次调用update方法之后至今的时间。你会需要用它来计算这个球在此帧中移动的位置。使用延迟时间来计算位置的方式比起每帧固定移动多少位置的方式来说,前者能够使游戏的帧率独立出来。这就意味着游戏可以在无视帧率的情况下,以一个相同的速度来运行。下一部分我们会需要util模块中的函数。在Ball.js的最上面import它。
var util = require('util');
回到刚创建的update方法中。我们在此调整球的位置(position)和速度(velocity)。我们需要现在的位置和速度的copy。幸运的是util模块中有一个copy方法来深度拷贝对象。在update方法的开始处获取位置和速度:
var pos = util.copy(this.get('position')), vel = util.copy(this.get('velocity'));
现在根据现在球的速度更新它的位置。简单的将延迟时间和x/y轴的速度相乘得到x/y轴的位置增量并加上原来的值。
pos.x += dt * vel.x; pos.y += dt * vel.y;
结束后更新位置:
this.set('position', pos);
重新加载程序,球就会运动。但是它会穿透过Bat(球拍)并消失在屏幕中。现在你的Bat.js文件看起来如下:
var cocos = require('cocos2d'); var geom = require('geometry'); var util = require('util'); var Ball = cocos.nodes.Node.extend({ velocity: null, init: function() { Ball.superclass.init.call(this); var sprite = cocos.nodes.Sprite.create({ file: '/resources/sprites.png', rect: new geom.Rect(64, 0, 16, 16) }); sprite.set('anchorPoint', new geom.Point(0, 0)); this.addChild({child: sprite}); this.set('contentSize', sprite.get('contentSize')); this.set('velocity', new geom.Point(60, 120)); this.scheduleUpdate(); }, update: function(dt) { var pos = util.copy(this.get('position')), vel = util.copy(this.get('velocity')); pos.x += dt * vel.x; pos.y += dt * vel.y; this.set('position', pos); } }); exports.Ball = Ball;
我们的下一个任务就是让球撞到球拍后弹开。我们会用到一个非常基本的矩形碰撞检测技术。在Ball类中创建一个空的方法叫testBatCollision.
testBatCollision: function() { }
然后在update方法的最下面调用它
this.testBatCollision();
为了辅助我们进行碰撞检测,我们需要一个所有节点都有的属性:(边界)boundingBox.。这个属性是geometry.Rect的实例。它的原点是节点元素的左上角,大小是节点元素的大小。我们会简单的检查球拍的边界和球的边界是否重叠,如果是的我们需要改变球的方向。
testBatCollision: function() { var vel = util.copy(this.get('velocity')), ballBox = this.get('boundingBox'), // The parent of the ball is the Breakout Layer, which has a 'bat' // property pointing to the player's bat. batBox = this.get('parent').get('bat').get('boundingBox'); // If moving down then check for collision with the bat if (vel.y > 0) { if (geom.rectOverlapsRect(ballBox, batBox)) { // Flip Y velocity vel.y *= -1; } } // Update position and velocity on the ball this.set('velocity', vel); }
注意我们通过Ball类的parent来方法Bat类,parent在此代表了main.js中的Breakout layer层。同时也检查了球是否正在向下掉落,这是用于防止球在转换方向之后的下一帧中依旧与球拍处于碰撞位置的情况发生。然后我们调用geometry模块的一个帮助方法来测试两个边界是否重叠(碰撞)。重新载入应用,球现在会与球拍产生碰撞,然后飞到屏幕的上面去。
我们让球与球拍碰撞了,但是我们还需要让它与屏幕边界碰撞。在Ball类中创建一个testEdgeCollision方法,然后在update方法的最后调用它:
this.testBatCollision(); this.testEdgeCollision();
这也是一个简单的函数。它所做的就是当球碰到了左、右或上边界就修改它的速度方向。
var vel = util.copy(this.get('velocity')), ballBox = this.get('boundingBox'), // Get size of canvas winSize = cocos.Director.get('sharedDirector').get('winSize'); // Moving left and hit left edge if (vel.x < 0 && geom.rectGetMinX(ballBox) < 0) { // Flip X velocity vel.x *= -1; } // Moving right and hit right edge if (vel.x > 0 && geom.rectGetMaxX(ballBox) > winSize.width) { // Flip X velocity vel.x *= -1; } // Moving up and hit top edge if (vel.y < 0 && geom.rectGetMinY(ballBox) < 0) { // Flip Y velocity vel.y *= -1; } this.set('velocity', vel);
第一部分代码是从Director中获取winSize。它的属性代表了canvas的大小(单位为像素)。我们需要知道它们来确定屏幕边界。其他部分的新代码:rectGetMinX, rectGetMaxX和rectGetMinY方法。这一组函数用来获取正方形边界的x和y像素值。例如:rectGetMaxX(rect)与rect.origin.x + rect.size.width一样。
重新载入应用,球就可以与左、右和上边界产生碰撞了,然后你就可以开始玩拍球的游戏了。当然了这还是远远不够的,要完成这个游戏还有很多的步骤,它的真实demo:http://cocos2d-javascript.org/demo?demo=breakout