适用于平板电脑和智能手机的贪吃蛇游戏






4.91/5 (36投票s)
本文介绍如何使用 Moscrif SDK 创建贪吃蛇游戏。
内容
引言
贪吃蛇是一款广为人知的手机游戏。贪吃蛇首次亮相是在 1997 年,人们通常从诺基亚 6110 认识这款游戏。从那时起,这款游戏经历了多次变化,现在是时候将其适配到具有触摸屏的现代设备了。此外,不用担心不同的移动平台,因为我们将向您展示如何仅用一份代码为多个平台开发。
游戏概述
我们都知道这款游戏的目标是什么,但为了确保我们步调一致;目的是用随机出现在屏幕上的食物来喂养蛇。为了让这款游戏与其他贪吃蛇游戏有所不同,我们添加了两种食物。第一种较小的食物会增加您 10 分,第二种较大的食物会增加 20 分。蛇在吃下每块食物后都会变长。
![]() | 小食物 - 分数增加 10 点 |
![]() | 大食物 - 分数增加 20 点 |
系统要求
如今,**大多数移动设备都使用 Andorid 和 iOS 平台**,但仍然存在 Bada OS 或 Windows Phone 等平台,它们正在不断增长市场份额。为了覆盖市场上大多数可用的设备,我们使用了 Moscrif SDK。使用 Moscrif SDK,开发人员可以使用一份代码库为 iOS、Android 和 Bada OS 创建应用程序。
我们的游戏硬件要求非常低;因此,即使在处理器仅为 420MHz 的设备上也能运行。这意味着我们的游戏也可以在**低端设备**和性能较低的设备上运行。
图形方案
每款游戏的开发都始于想法转化为图形方案。它定义了游戏的外观以及应包含的游戏对象。它还指示了游戏的功能。这款游戏对所有年龄段都友好,因为它不使用任何有争议的设计,而是色彩和谐地搭配,视觉效果令人愉悦。
图片:我们游戏的图形方案
用户界面
正如您在上面的图片中看到的,图形显示了游戏的两个部分 - 引言部分,即第一个菜单场景,然后是第二个场景,即实际的游戏。
- 菜单 - 这是引言场景。它在应用程序的开始或结束时显示,以及游戏暂停时显示。用户可以开始新游戏、继续暂停的游戏或退出应用程序。
- 游戏 - 这是进行游戏的主要场景。它由分布在游戏场景中的精灵对象组成。简单来说,蛇在屏幕上移动并吃食物。用户可以通过点击屏幕朝蛇想去的方向来控制蛇。
菜单
菜单是引言场景。它在应用程序启动时显示,并允许用户开始新游戏、继续游戏(如果已暂停)或退出游戏。由于**苹果使用退出按钮的应用程序政策**,因此 iOS 版本未添加此按钮。
菜单由 MenuScene 类创建。它继承自 Scene 类。当创建类的实例时,会调用 init 函数。菜单场景类通过父类的 init 函数进行覆盖。但是,父类的 init 函数不会自动调用,需要通过 super.init() 命令调用它以确保正确的对象创建。菜单包含三个按钮,提供操作功能:继续、新游戏或退出。
图片:菜单元素
所有按钮都由单独的函数创建。但是,如前所述,请确保在 iOS 版本中不添加退出按钮。苹果不会接受。
function init()
{
super.init();
this._computeScale(); // compute scale
this._loadBg(); // load background
this._createContinueBttn(); // create continue button
this._createTouchGameBttn(); // create new touch game button
// check if application does not run on iOS
if (System.OS_NAME != #iOS)
this._createQuitGameBttn(); //create quit button
}
示例:菜单场景 init 函数
按钮是作为 GameButton 类的实例创建的,它有两个状态:按下和“正常”。两者都作为两个帧放在同一个源文件中。默认情况下,显示第一个帧。按下按钮时,显示下一个帧。一个帧的坐标由 frameWidth 和 frameHeight 属性设置。GameButton 在被点击时还会调用 **onClick** 事件。
function _createTouchGameBttn()
{
this._bNewGameTouch = new GameButton({image:M_NEW_GAME_TOUCH, frameWidth:327, frameHeight:66, scale : this._scale});
this._bNewGameTouch.x = System.width/2;
this._bNewGameTouch.y = this._bContinue.y + this._bContinue.scaledHeight + M_VERTICAL_SPACE;
this._bNewGameTouch.onClick = function()
{
game.gameScene = new GameScene();
game.push(game.gameScene, new SlideToLeft({duration:1000,transition:Animator.Transition.bouncy}));
this super.showContinueBttn();
}
this.add(this._bNewGameTouch);
}
示例:_createTouchGameBttn 显示了如何创建游戏按钮
添加的按钮由 Scene 类自动绘制。但是,背景必须在 draw 函数中手动绘制。背景图像已在 init 函数中加载。此外,在 draw 函数中必须调用父类的 draw 函数,因为它绘制了添加的按钮和其他元素。
function draw(canvas)
{
// draw background scene
canvas.drawBitmap(this._background, 0, 0);
// call draw function on other objects in scene
super.draw(canvas);
}
示例:draw 函数
游戏
游戏由许多部分组成,如蛇、食物、各种按钮等。它还有许多功能,如碰撞检测或食物分配。所有对象和功能的基础是 Game Scene 类。
图片:游戏元素
游戏场景由背景、菜单按钮、分数、蛇和掉落物(蛇的食物)组成。在 **init 函数**中,我们创建所有必需的对象并创建初始掉落物分布。
function init()
{
super.init();
this._initVariables(); // init helper variables
this._loadBg(); // load background image
this._createMenuBttn(); // create and add to the scene menu button
this._createScore(); // create and andd to the scene score
this._loadStage(); // load stage from xml
this._createBigDrops(); // create and distribute big drops
this._createDrops(); // create and distribute drops
this._createSnake(); // create snake object
}
示例:init 函数创建所有必需的对象
**_createDrops** 函数将掉落物分配到游戏区域。掉落物被分配到随机位置。但是,我们必须确保两个食物不会重叠。
首先,我们创建一个新的 Drop 对象。然后 - 在 while 循环中 - 我们尝试在屏幕上找到一个位置,我们的对象不会与其他对象发生碰撞。我们通过 rand(Integer) 函数获取随机位置。我们必须检测是否与其他对象没有发生碰撞。如果一切正常,我们可以使用 Stop 变量跳出 while 循环。最后,我们将掉落物添加到我们的 food 数组和场景中。
function _createDrops()
{
for (var i = 0; i < this.stage.drop_count; i++) {
var b = new Drop();
// Random position
var stop = false;
while (!stop) {
stop = true;
b.x = (1+rand(this.width/b.width-2))*b.width-10;
b.y = (1+rand(this.height/b.height-2))*b.height-10;
for (var i in this.food)
if (b.intersectsBounds(i))
stop = false;
}
// Add object to the container
this.food.push(b);
this.add(b);
}
}
示例:将掉落物分配到随机位置
游戏场景只有一个按钮,允许用户返回游戏菜单。它也由 GameButton 类创建。另一个 UI 元素是**分数指示器**。
分数
分数是一个重要的功能,它使游戏更具挑战性。在这款游戏中,分数取决于吃到的食物的数量和类型,目标是在不接触墙壁或蛇自身身体的情况下获得最高分数。
如何计算得分?
每次蛇吃到食物时,分数都会重新计算。我们可以认为蛇头是吃食物的部分。屏幕上分配的所有食物都被推入一个名为 food 的数组。可以访问所有食物对象以及蛇头,这使我们能够检查食物是否被吃掉。我们需要通过 intersectsBounds 函数检查是否有食物在头部下方。
/**
Check intersects by object
@param Gameobject obj
@return Boolean
*/
function intersectsBounds(obj)
{
assert obj instanceof GameObject;
var tx = this._x;
var ty = this._y;
var ox = obj._x;
var oy = obj._y;
return (
tx + this.width > ox && tx < ox + obj.width &&
ty + this.height > oy && ty < oy + obj.height
);
}
示例:intersectsBounds 函数 - 检查对象是否重叠
一旦我们能够检查对象是否重叠,调用此函数处理所有食物对象就很简单了。最佳放置位置是在 onProcess 事件中,该事件也管理着移动。如果头部与某个食物重叠,我们将分数属性(包含当前分数)的值增加已吃食物的值。
/**
Event called within Scene onProcess
@param GameScene sender
*/
function onProcess(sender)
{
...
// Check collisions with other parts of snake
for (var b in sender.snake.body)
if (this.intersectsBounds(b))
sender.endGame();
// Check collisions with food
for (var f in sender.food)
if (this.intersectsBounds(f)) {
// Set new random positions
var stop = false;
while (!stop) {
stop = true;
f.x = (1+rand(sender.width/CELL_WIDTH-2))*CELL_WIDTH -10;
f.y = (1+rand(sender.height/CELL_HEIGHT-2))*CELL_HEIGHT -10;
for (var i in sender.food)
if (f != i && f.intersectsBounds(i))
stop = false;
for (var i in sender.walls)
if (f.intersectsBounds(i))
stop = false;
}
sender.snake.addPart(); // Add new part to the body.
if (sender.score.value < 300)
sender.snake.timer.sleep -= f.speedValue; // It will be more often call method onProcess and snake will be faster.
sender.score.value += f.score;
break;
}
...
}
示例:Heat.intersectsBounds 函数 - 检查对象是否重叠
贪吃蛇
贪吃蛇是游戏的主要角色,其移动由触摸屏幕控制。
贪吃蛇由 **Head、Body 和 Tail** 精灵对象组成,它们有一个共同的父类 Part。Part 类继承自 Sprite 类,并具有 direction 属性,该属性用于描述蛇的每个部分。这是一个非常重要的属性,因为它定义了移动方向。Snake 类借助 Delay 和 Switch 对象同步这些对象。
Snake 类在构造函数中创建上述对象。 **addParts(count)** 函数创建 Body 对象并将其保存到数组中。
function this(x, y, count, direction)
{
this.body = []; //Array for save snake's parts
// Create Timer
this.timer = new Delay();
// Create Switch
this._switch = new Switch(this);
// Create head
this.head = new Head(); //snake's head
this.head.x = x;
this.head.y = y;
// Create tail
this.tail = new Tail();
this.tail.x = x;
this.tail.y = y;
this.tail.back(); // move with tail about one position back
this.direction = direction;
this.addParts(count);
}
示例:Snake 构造函数
贪吃蛇由身体部分组成(身体部分可以有很多),这意味着蛇是逐渐转弯的。首先是蛇头转弯,然后是第一个身体部分转弯,第二个身体部分转弯等等,最后是蛇尾转弯。这些部分,包括头部和尾部,可以在一个以上的地方转弯。
每次用户触摸屏幕(且游戏处于活动状态)时,都会调用 turnTouch(x,y) 函数。此函数首先检查蛇(其头部)当前是水平移动还是垂直移动。如果它是水平移动,它会检查用户是触摸了蛇头上方还是下方。如果用户触摸蛇头上方,则向上移动。如果用户触摸蛇头下方,则向下移动。如果蛇是垂直移动,则进行相同的过程。
图片:turnTouch 函数
/**
Change direction of snake by x, y coordinates.
@param x int
@param y int
*/
function turnTouch(x,y)
{
if (this.direction == #left || this.direction == #right) {
if (y<this.head.y) this.direction="#up;">this.head.y)
this.direction = #down;
}
else if (this.direction == #up || this.direction == #down) {
if (x<this.head.x) this.direction="#left;">this.head.x)
this.direction = #right;
}
}
</this.head.x)></this.head.y)>
示例:turnTouch 函数
direction 属性向 switch 对象添加了一个新的边。该边允许蛇在放置该边的位置进行转弯。
/**
Property direction.
@get return direction of snake
@set set direction in head and add change direction to the switch
*/
property direction(value)
{
get return this.head.direction;
set {
if (this.direction == #left && value == #up)
this._switch.addEdge(#left_up);
if (this.direction == #left && value == #down)
this._switch.addEdge(#left_down);
...
this._switch.addEdge(#down_right);
this.head.direction = value;
this._switch.add(value); // Change directions in the rest of the snake
}
}
示例:direction 属性
Switch
switch 对象管理蛇的转弯。每次蛇转弯时,都会放置一个边。addEdge 函数仅将新的 SwitchItem 放入 Switch 对象中的一个数组中。/**
Add switcher, which change image of part to edge image.
@param direction symbol
*/
function addEdge(direction)
{
this.array.push(new SwitchItem(direction, 0))
}
示例:addEdge 函数
边被放置在蛇应该转弯的位置。转弯由 Switch 对象的 Refresh 函数管理。当蛇从 snake 对象的 onProcess 函数移动时,每次都会调用此函数。此函数应用所有边,并在不需要时删除边。其行为如图所示。
图片:Refresh 函数的行为
refresh 函数的代码非常简单
/**
It is loop which should call in onProccess function.
*/
function refresh()
{
var del = 0;
var j = 0;
// go throw all edges (SwitchItems) in the array
for (var i in this.array) {
var it = i.next();
if (it==this.snake.body.length)
del++;
else if (it >= 0) {
// apply directory to the body
this.snake.body[it].direction = i.direction;
}
j++;
}
if (del > 0) // delete switchs which are on the end
for (var i = 0; i < del; i++) {
this.snake.tail.direction = this.array[0].direction;
this.array.remove(0);
}
}
示例:refresh 函数
总结
现在您知道了如何使用 Moscrif SDK 创建移动行业中最受欢迎的游戏之一。这将确保您减少工作量并获得最佳结果。我们的项目专注于支持 iOS、Android 或 Bada 的手机和平板电脑。这款游戏广泛的设备支持和分辨率独立性为其带来了巨大的市场定位机会。不要浪费时间为每个移动平台单独开发应用程序,试试 Moscrif 跨平台开发工具(**可在此免费下载**)。
更多资源
更多关于 Moscrif 的示例、演示和信息可以在 www.moscrif.com 上找到