打砖块





5.00/5 (12投票s)
本文介绍如何使用一份代码为 iOS、Android 和 Bada 创建 Breakout 游戏。

目录
简介
Breakout 是一款著名的街机游戏,于1976年5月13日由 Atari 公司首次推出。截至今年,这款游戏已有许多不同版本的不同平台。去年 Atari 推出了适用于 Apple 移动设备的 Breakout Boost 游戏。然而,现在我们为您提供如何为目前最常用的平台:iOS、Android 或 Bada 创建此游戏的说明。
游戏的主要玩法是用一个球击落屏幕顶部五行中的所有砖块。球开始时会获得一定的速度,然后被屏幕底部的挡板反弹。为了让游戏更有趣,此实现包含四个关卡,随着玩家在游戏中前进,砖块会停留更长时间。当球击中第一关的砖块时,它会消失。当它击中第二关的砖块时,它会变成第一关的砖块,依此类推。
![]() |
类型 1 - 当砖块被球击中时,它会消失 |
![]() |
类型 2 - 当砖块被球击中时,它会变成砖块类型 1 |
![]() |
类型 3 - 当砖块被球击中时,它会变成砖块类型 2 |
![]() |
类型 4 - 当砖块被球击中时,它会变成砖块类型 3 |
系统要求
正如我之前提到的,我们的游戏运行在所有常用平台(根据 G1 2012 的统计数据)。要使用一份代码创建运行在三个平台上的游戏,我们使用 Moscrif SDK。 Moscrif SDK 的硬件要求非常低。即使是仅配备 420MHz 处理器的设备也能运行。这意味着我们的游戏将运行在所有带有 iOS、Android 或 Bada 的常用设备上。
用户界面与图形
游戏发生在太空中,配有合适的动画(闪电效果),从而增强了用户体验。游戏由两部分组成:游戏部分和菜单。两者都被创建为 Moscrif 的独立 Scene 类。
菜单提供了所有必要的操作,如开始新游戏、继续之前开始的游戏和退出游戏。由于 Apple 的应用政策,iOS 版本中没有退出按钮。
游戏场景只有一个返回菜单场景的按钮。游戏的其余组件是砖块、球和挡板。
图片:图形提案

物理引擎
游戏中的球遵循物理定律运动。它获得一定的速度,然后从挡板反弹。游戏中也应用了少许重力。为了模拟这种物理行为,我们使用了 Moscrif SDK 支持的box2d 物理引擎。该引擎还可以在其他平台,如Nintendo DS 或 Wii 上看到。
世界
box2d 物理引擎最重要的特点是世界和物理体。世界为所有物理体创建背景。世界可以有重力,重力可以在两个轴(x、y)上指定。世界始终具有宽度,相当于现实世界中的10 米。世界中的所有对象都会被缩放以确保这个宽度。比例因子表示一米有多少像素。box2d 还使用自己的坐标系,该坐标系从左下角开始,以米为单位。

幸运的是,Moscrif 框架通常使用正常的像素坐标,并且转换会自动完成。
物理体
物理体代表在世界中相互作用的所有实际对象/事物。有三种类型的物理体,它们的行为不同。
- #static(静态): 静态物理体在模拟下不移动,并且具有无限质量。静态物理体可以通过 setPosition 方法手动移动。静态物理体速度为零,并且不与其他静态或运动学物理体碰撞。
- #kinematic(运动学): 运动学物理体根据其速度在模拟下移动。运动学物理体不受力影响。它们可以被用户手动移动,但通常运动学物理体由需要设置的速度来移动。运动学物理体具有无限质量,但它不与其他静态或运动学物理体碰撞。
- #dynamic(动态): 动态物理体被完全模拟。它可以通过 setPosition 手动移动,但通常根据受到的力移动。动态物理体可以与所有类型的物理体碰撞,并且始终具有有限的、非零的质量。如果您尝试将动态物理体的质量设置为零,它将自动获得一公斤的质量。
正如您所见,并非每个物理体都与其他物理体碰撞。下表显示了哪些物理体相互碰撞。
物理体类型 | #静态 | #动态 | #运动学 |
---|---|---|---|
#静态 | |||
#动态 | |||
#运动学 |
相互碰撞 | |
---|---|
不相互碰撞 |
物理体有许多影响其物理行为的属性。直接影响物理体行为的三个属性是弹跳、密度和摩擦。
密度是一个影响物理体质量的属性。
摩擦是抵抗相对运动的元素相互滑动的作用力。
弹跳属性影响弹跳的大小。值为 0.0 意味着物理体不会从其他物理体上弹起。值为 1.0 意味着物理体将以与落下的相同速度弹起;然而,有可能增加弹跳率,球会以更大的速度弹起。
开发过程
游戏位于游戏场景中。游戏场景继承自PhysicsScene,它创建了 box2d 世界。如果通过 create 函数创建了 PhysicsScene 的实例,则世界会自动创建,否则必须在 init 函数中手动创建。在 Moscrif 中,box2d 世界类的所有成员都映射到 PhysicsScene 类。这意味着 Scene 表现得像 box2d 世界。
砖块
砖块由 Brick 类表示。它继承自 PhysicsSprite 类,这意味着它们表现得像 box2d 物理体。砖块的物理体类型设置为静态。这意味着它与其他动态物理体(球)交互,但不会根据重力移动。砖块有四种状态(等级)。当球击中砖块时,等级会降低。所有物理属性都在 init 函数中设置。物理体摩擦和密度为零。静态物理体的密度始终为零(无论如何设置)。弹跳属性影响从砖块上的弹跳,仅设置为 0.1,因为球的弹跳已经足够大(1.0 - 完全弹跳)。
当砖块被击中时,砖块会降级。如果砖块的等级小于 1,砖块就会消失。
示例 hit 函数。降低状态或禁用砖块
function hit()
{
if (this.disabled == false)
// disable brick if tis state (level) is less then one
if (this.state < 1) {
this.disabled = true;
this.scene.brickCount--;
this.hide(this);
// decrease the brick's state (level)
} else {
this.state -= 1;
}
}
砖块通过平滑动画显示和隐藏。它只改变 alpha 值。动画由 Animator 类创建。Animator 类实现了动画的所有数学背景。使用此类,动画不必具有线性行为。根据所需行为,Animator 对象调用 addSubject 函数指定的调用函数。调用函数只有一个参数:state,它指定动画中的当前位置。
Moscrif 在 transition 属性中提供了许多各种行为。在 show 函数中,创建了一个持续 500ms 的 animator,并使用由 easyIn 标识的 transition。它开始时缓慢,然后在动画结束时快速加速。
示例: 以平滑动画显示砖块
// animate brick wehn it is shown
function show(obj)
{
// create animator
var animator = new Animator({
transition: Animator.Transition.easeIn, // start up slowly and then quickly speed up at the end of the animation
duration: 500, // length of animation in miliseconds
});
animator.addSubject(function(state) { // state starts from 1.0 to 0.0
obj._paint.alpha = 255-(state*255).toInteger();
});
animator.reverse();
}
然而,easyIn 并不是唯一的 transition 类型。Moscrif 提供了许多其他类型。它们的行为显示在接下来的三张图中。
图片: Animator 转换类型



挡板
挡板用作反弹球以击中砖块的板。它也作为从 PhysicsSprite 继承的独立类创建。它的类型是静态的,因为它也不会根据世界的重力和其他力移动。当用户在屏幕上拖动手指时,它通过 setPosition 函数手动移动。为了让游戏更有趣,挡板与闪电动画连接。它只有三个帧,但 fps 设置为 10,因为闪电效果通常很短。真实的闪电不是平滑动画。
图片: 动画帧

帧在定时器类使用的时间间隔内定期更改。帧在 _electricShock 函数中更改,该函数会在定时器滴答事件上循环调用。在第一帧和最后一帧之间的效果间隔为 100ms。第一帧和最后一帧之间有一个更长的时间延迟。
示例: 创建闪电动画
// create electric animation
function _eletricShock(frame = 0)
{
// change current frame
this.frame = frame;
frame++;
// if animation is on the last frame move to the first
if (frame == 3)
frame = 0;
this._timer = new Timer(100, false); // interval make no sense here
if (frame != 1)
this._timer.start(100);
else
this._timer.start(res.values.electricShockMin + rand(res.values.electricShockGap));
this._timer.onTick = function(sender) {this super._eletricShock(frame)};
}
GameScene
所有游戏元素都组合在 GameScene 中,包括球、砖块和挡板边界。它们在 GameScene 中创建。边界可防止球离开屏幕。
示例: 在屏幕周围创建边界
function _createMantinels()
{
// Ground
var (width, height) = (System.width, 1);
this._ground = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, width, height); // density, friction, bounce
this._ground.setPosition(System.width/2, System.height - (this._ground.height/2));
// Left mantinel
var (widthML, heightML) = (1, System.height);
this._leftMantinel = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, widthML, heightML);
this._leftMantinel.setPosition(this._leftMantinel.width/2, System.height/2);
// Righ mantinel
var (widthMR, heightMR) = (1, System.height);
this._rightMantinel = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, widthMR, heightMR);
this._rightMantinel.setPosition(System.width - (this._rightMantinel.width/2), System.height/2);
// Top mantinel
var (widthMT, heightMT) = (System.width, 1);
this._topMantinel = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, widthMT, heightMT);
this._topMantinel.setPosition(System.width/2, (this._topMantinel.height/2));
}
当两个物理体碰撞时,会发生两个事件。第一个事件是开始接触时的 onBeginContact,第二个是接触结束时的 onEndContact。这些事件被映射到 _onBeginContactHandler 和 _onEndContactHandler 函数。
在 _onBeginContactHandler 函数中,检查是否有东西撞到了地面。如果是,那只能是球,然后销毁它。由于 onBeginContact 事件在每个时间步最多调用一次,即使出现更多接触,也需要在同一个函数中检查所有接触。两个接触事件的第二个对象是 b2Contact 对象,它携带有关所有接触的信息。相互作用的物理体可以通过 getBodyA 和 getBodyB 函数找到。b2Contact 对象是所有接触的列表。使用 getNext 函数移动到下一个接触。检查所有接触的最佳方法是进行循环。
示例: onBeginContact 事件处理程序
function _onBeginContactHandler(sender, contact)
{
// if the ball do not exists the contact is irrelevant - do nothing
if (!this._ball) return;
// get the first contact
var current = contact;
while (current) {
// get the bodies in the contact
var bodyA = current.getBodyA();
var bodyB = current.getBodyB();
// check if something hit the ground (it can be only ball)
if (bodyA == this._ground || bodyB == this._ground)
// destroy the ball
this._bodiesToDestory.push(this._ball);
// get the next contact (they can be more contacts during the one step)
current = current.getNext();
}
}
_onEndContactHandler 的工作原理类似,但它会检查是否有东西撞到了砖块。如果是,它会降低砖块的等级并播放声音。
示例: onEndContact 事件处理程序
function _onEndContactHandler(sender, contact)
{
// if the ball do not exists the contact is irrelevant - do nothing
if (!this._ball) return;
var current = contact;
// get the first contact
while (current) {
// get the bodies in the contact
var bodyA = current.getBodyA();
var bodyB = current.getBodyB();
var existing = this._bricks.filter(:x { return x == bodyA; }); // lamba function, the same as ".filter(function(x) { return x == bodyA; })"
if (existing.length != 0) {
bodyA.hit();
if (this._enableSounds) this._wavPaddle.play();
return;
}
existing = this._bricks.filter(:x { return x == bodyB; });
if (existing.length != 0) {
bodyB.hit();
if (this._enableSounds) this._wavPaddle.play();
return;
}
if (this._enableSounds && (bodyA == this._paddle || bodyB == this._paddle))
this._wavBall.play();
// get next contact
current = current.getNext();
}
}
物理体不会直接在回调函数中移除。在 box2d 中,不允许在 box2d 回调函数中移除或禁用物理体。尽管此功能在 box2d 官方文档中也有直接提及,但此问题在论坛和其他非官方教程中也经常被讨论。为了确保安全地移除物理体,它们只会被添加到数组中。在 process 函数中(大约每 25ms 运行一次),会搜索数组并从世界中移除所有物理体。
示例: 如果需要移除某些物理体,则从 process 事件调用 destroy body 函数
function process()
{
var timeStep = 1.0 / 40.0;
// recalculate physics world. All objects are moved about timeStep
if (!this.paused)
this.step(timeStep, 4, 8);
// remove bricks from the world
if (this._bodiesToDestory.length != 0)
this._removeBricks();
// inactive bricks in the world
if (this._bodiesToInactive.length != 0)
this._inactiveBricks();
// if user finished the level move to the next level
if (this.brickCount == 0 && this.paused == false){
this._nextLevel();
this.paused = true;
}
}
搜索数组和移除砖块是在单独的函数中完成的。
示例: 从场景中移除砖块
function _removeBricks()
{
// Remove touched bricks
for(var body in this._bodiesToDestory) {
var existing = this._bricks.filter(:x { return x == body; });
if (existing.length != 0)
this._bricks.removeByValue(body);
// GAME OVER
if (this._ball == body) {
this._gameStarted = false;
this._createBall();
this._paddle.setPosition( this._paddleX, this._paddleY);
}
this.destroyBody(body);
}
// zero the array
this._bodiesToDestory = [];
}
菜单
游戏还有一个简单的菜单,它被创建为一个单独的场景。它只包含三个按钮:play、continue 和 quit。iOS 版本没有 quit 按钮,因为 Apple 的应用程序政策不允许。所有按钮都由单独的函数创建。
示例: 创建 play 按钮
function _createPlayButton(top)
{
// create new instanco of GameButton with images from the resources
var button = new ImageButton({image: res.img.playButton, x:System.width/2, y: top, frameWidth: res.img.playButton.width, frameHeight: res.img.playButton.height / 2});
// set onClick event - start new game
button.onClick = function(s)
{
// create new game scene
game.game = GameScene.create(0.0, -0.5, { calledClass: GameScene } );
// initialize new game
game.game.removeAllBricks();
game.game.brickCount = 0;
game.game.paused = true;
game.game.visible = true;
game.game.level = 0;
game.game.start();
game.push(game.game, new SlideToBottom());
}
return button;
}
当应用程序启动时,按钮会以惊人的推入动画显示。要达到所需的效果,需要组合多个 animator。需要更改 alpha 值和缩放。使用 AnimatorChain 来组合更多 animator。
示例: 创建脉冲动画
function _pulseAnimation(obj) // fade in then pulse
{
obj.alpha = 1; // start form invisible state
var fade = Transition.to(obj, {
transition : Animator.Transition.easeIn,
duration : 400,
alpha : 255, // transparency - custom attribute
}, false); // false = don't play autimatically
var scale1 = Transition.to(obj, {
transition : Animator.Transition.easeInOut,
duration : 150,
scale : 0.8, // smothly resize the object
}, false);
var scale2 = Transition.to(obj, {
transition : Animator.Transition.easeInOut,
duration : 200,
scale : 1.3, // smothly resize the object
}, false);
var scale3 = Transition.to(obj, {
transition : Animator.Transition.easeInOut,
duration : 100,
scale : 1.0 // smothly resize the object
}, false);
// play all animations gradually
new AnimatorChain([fade, scale1, scale2, scale3]).play();
}
总结
现在是时候将游戏移植到移动平台了。本文介绍了如何在 Moscrif SDK 中创建它。使用 Moscrif SDK可以节省大量时间,因为它只需要为所有平台编写一份代码。此游戏适用于大量设备,因为它支持 iOS、Android 和 Bada 智能手机以及平板电脑。
更多资源
更多关于 Moscrif 的示例、演示和信息可以在 www.moscrif.com 上找到。