Платформенный скроллер

Исходный файл: Platform.fla

После первой волны аркадных игр, которая спала где-то в начале 80-х, следующим значительным течением было появление игр типа платформенный скроллер. Этот жанр приобрел ширенную популярность после появления серии игр "Mario Bros" от компании Nintendo. В конце 80-х платформенные скроллеры были абсолютным фаворитом среди консольных игр. Игры этого типа продолжают выходить и сегодня.
Игровой процесс типичного скроллера состоит в следующем: герой игры передвигается взад и вперед по двухмерному миру (вид сбоку). Игровое
пространство имеет несколько горизонтальных уровней - платформ, и игрок может прыгать и перемешаться по ним. Цель состоит в собирании различных предметов, за которые начисляются очки, и в том, чтобы избегать (а также уничтожать) других созданий игрового мира.
На рисунке 16.8 показан простенький пример скроллера с лисой в главной роли. Лиса может перемещаться туда-сюда по горизонтали и прыгать по платформам. При этом ей необходимо собирать орехи и избегать кроликов.

Рисунок 16.8 Реализованный во Flash платформенный скроллер содержит все основные элементы этого жанра

Задача проекта

Платформенный скроллер может быть довольно глобальным проектом. Противников (в нашем случае кроликов) можно атаковать, например, напрыгивая на них, некоторые объекты могут давать игроку специальные возможности, такие как увеличение скорости или возможность поражать неприятеля, который неуязвим в остальных случаях. Мы же постараемся сделать максимально простую игру, сосредоточив усилия на воплощении основных особенностей жанра. Лиса будет иметь возможность перемещаться взад-вперед и прыгать. Платформы в виде блоков будут препятствовать перемещению игрока по горизонтали, так что ему придется запрыгивать на них и проделывать часть пути поверху.
Перемещение игрока в этой игре означает прокрутку всего игрового пространства, в то время как игрок на самом деле все время находится в центре экрана. Ощущение движения лисы возникает за счет того, что в игре движется все, кроме самой лисы.
Каждый орех добавляет игроку 100 очков. Лиса должна собирать орехи и избегать кроликов.
Дизайн игрового пространства очень легко усложнить. После того как мы закончим разбирать процесс создания этого ролика, вы можете сделать собственный вариант уровня игры.

Подход

Пространство игры представляет собой массив небольших объектов. Эти объекты имеют свойство type, соответствующие клипу, который будет использоваться. Клип имеет свойства х и у, определяющие его местоположение. Вот, например, как может выглядеть такой объект {type: "box", х:100, у: 0}.
Игрок начинает в позиции (0, 0). Если он передвигается вправо, то его позиция по х увеличивается. Если блок находится в позиции 0 по х, а игрок - в позиции 100, это значит, что блок располагается на 100 пикселов правее игрока. Если же игрок смешается в позицию 10 по х, то блок будет находиться на 90 пикселов правее.
Лиса не перемешается по горизонтали, а всегда находится в центре экрана. Все объекты прорисовываются в соответствии с их расстоянием от лисы. Когда речь идет о передвижениях лисы, имеется в виду ее виртуальное перемещение по пространству игры, а не по экрану.
Определение областей, в которых возможно (невозможно) передвижение лисы, является важнейшей задачей этой игры. Если лиса упирается в блок, она должна остановить свое перемещение в этой позиции. Эта задача реализована посредством постоянного сравнения положения лисы с положениями всех блоков. Если блок находится к игроку ближе других блоков в этом направлении, тогда его координаты принимаются : за самую далекую точку в данном направлении, до которой возможно перемещение лисы. Например, если блок находится на расстоянии 200 пикселов вправо от лисы, значит, лиса не может быть перемешена далее чем на 200 пикселов вправо. Если же в процессе проверки всех остальных блоков обнаружен блок, находящийся на расстоянии 100 пикселов от лисы, то значение 100 принимается как максимальное для возможного перемещения лисы в данном направлении.
То же самое происходит и по вертикали. Когда лиса прыгает, ей передается значение скорости по вертикали. При каждом обращении к кадру эта скорость регулируется за счет воздействия силы тяжести, так что лиса постепенно замедляется, а затем падает обратно вниз. Все объекты игрового пространства проверяются для определения наивысшей и наинизшей точек возможного движения лисы. Когда лиса достигает низшей точки, вертикальное движение прекращается. Это может быть самый низкий уровень ("земля") или одна из платформ других уровней.

Подготовка ролика

Ролик содержит всего четыре клипа. Клип "fox" содержит некоторое количество кадров. В первом кадре изображена неподвижная лиса, далее некоторое количество кадров изображают анимацию бегущей (в одном направлении) лисы, и последние кадры изображают лису в прыжке. Имя экземпляра этого клипа на рабочем столе также "fox"; для экземпляра выставлен масштаб 25%.
Клип "box" - это просто коричневый квадрат размером 50x50. Этот клип содержит один кадр.
Клип "acorn" начинается с кадра со статичным изображением ореха. В этом кадре находится команда stop О . Остальная часть клипа изображает постепенное появление числа "100". Эта анимация проигрывается, когда игрок берет орех и ему начисляются очки.
Клип "bunny" состоит из двух кадров изображающих кролика, шевелящего лапками. Эта анимация работает независимо от направления перемещения кролика.
Клипам "box", "acorn" и "bunny" необходимо присвоить имена в панели Linkage Properties, чтобы был возможен их экспорт.
В главной временной шкале находятся три кадра. Первый содержит инструкции и кнопку Play, последний содержит сообщение "Game Over". В среднем кадре "Play" находится лиса и фон, изображающий землю. Все остальные элементы будут созданы программно при запуске игры.

Создание кода

За небольшими исключениями весь код этой игры находится в кадре "Play". Он начинается с вызова функции startGame. Она инициализирует набор констант таких как скорость лисы и кролика, устанавливает начальное положение лисы и вызывает функции createWorld и creatObjects, которые создают все элементы игрового пространства.

startGame();
stop();
function startGame() {
// Устанавливаем константы,
floor = 350;
foxSpeed = 10;
bunnySpeed = 2;
jumpPower = 60;
// Задаем параметры лисы.
foxPos = {x:0,y:0};
fallSpeed = 0;
falling = false;
fox.swapDepths(999);
// При каждом обращении к кадру вызываем moveFox.
_root.onEnterFrame = moveFox;
// Создаем элементы игры.
createWorldf);
createObjects() ;}

Функция creatWorld создает массив objects и заполняет его положением всех блоков, орехов и кроликов. Она также задает глобальную переменную worldEnd, которая определяет правую границу игрового пространства.

function createWorld() {
objects = new Array();
objects.push({type:"box", x:250, y:0});
objects.push({type:"box", x:300, y:0});
objects.push({type:"box", x:500, y:0});
objects.push({type:"box", x:550, y:0});
objects.push({type:"box", x:600, y:0});
objects.push({type:"box", x:650, y:0});
objects.push({type:"box", x:700, y:0});
objects.push({type:"box", x:550, y:50});
objects.push({type:"box", x:600, y:50});
objects.push({type:"box", x:650, y:50});
objects.push({type:"box", x:850, y:0});
objects.push({type:"box", x:900, y:0});
objects.push({type:"box", x:1050, y:100});
objects.push({type:"box", x:1100, y:100});
objects.push({type:"box", x:1150, y:100});
objects.push({type:"acorn", x:150, y:0});
objects.push({type:"acorn", x:275, y:200});
objects.push({type:"acorn", x:1100, y:250});
objects.push({type:"bunny", x:400, y:0});
objects.push({type:"bunny", x:1200, y:0});
worldEnd = 1400;
}

Если вы собираетесь сделать пространство игры больше или, возможно, хотите добавить новые уровни, то, вероятно, вы захотите изменить функцию creatworld так, чтобы она считывала данные из внешнего текстового файла вместо того, чтобы создавать каждый элемент в отдельной строке кода. Текстовый файл может определять тип и расположение каждого элемента в отдельных позициях так, как это было сделано в главе 12.

После создания массива objects функция creatObjects просматривает его в цикле и создает все соответствующие клипы.

function createObjects() {
for(var i=0;i _root.attachMovie(objects[i].type,"object "+i,i);
} }

Функция moveFox - это главный движущий механизм всей игры. В ней идет ряд проверок, которые отслеживают появление определенных событий в игре.
Сначала вызывается функция determineBounds, которой передается значение координат лисы (1). Эта функция возвращает максимальные значения расстояний вверх, вниз, вправо и влево, на которые может передвигаться лиса, не натыкаясь на какой-нибудь блок.
Далее, функция moveFox проверяет, есть ли пустое место под лисой и не находится ли лиса в состоянии падения, что определяется с помощью переменной falling (2). Если это так, то лиса начинает падать.
Если falling равняется true, то вызывается функция checkFall для обработки вертикального движения (3).
Далее, проверяется не нажаты ли стрелки "вправо" и "влево" (4). Если да, то проверяются оба значения foxBounds.left и foxBounds.right, чтобы определить, есть ли место для движения лисы. Также проверяются границы игрового пространства - 0 и worldEnd, - чтобы лиса не выходила за их пределы. Переменная moving принимает значение true только если нажата клавиша со стрелкой. Эта переменная определяет, какая часть клипа "fox" будет проигрываться.
Лиса прыгает при нажатии пробела (5). Прыжок противоположен падению, поэтому переменная fallSpeed принимает положительное значение (когда лиса падает, ее значение меньше ноля). Если в момент нажатия пробела лиса стоит, то запускается анимация прыжка лисицы. Если же лиса находится в движении, когда нажат пробел, то клип лисы остается в позиции шага (то есть она прыгает в том положении, в котором ее застало нажатие на пробел).
Следующий фрагмент кода отвечает за анимацию лисы (6). Если лиса, движется и не падает вниз (не прыгает вверх), то клип лисы переходит к следующему кадру. Благодаря этому ноги лисы "шагают". Если же лиса движется или падает, то клип лисы переходит в первый кадр с изображением стоящей лисы.
В то время как горизонтальная позиция лисы постоянно находится в центре экрана, ее вертикальное положение может меняться. Вертикальное положение лисы задается свойством foxPos.y, а земля находится в вертикальной позиции 0. Значит, чтобы получить свойство _у клипа лисы, мы должны вычесть foxPos.y из значения переменной floor (7).
В конце функции moveFox вызываются еще три функции: moveBunnies отвечает за движение кроликов, drawObjects перерисовывает блоки, кроликов и орехи в соответствии с новым положением игрового поля относительно лисы, а функция getAcorns проверяет, не съела ли лиса какой-нибудь из орехов.

function moveFox() {

(1) // Определяем границы возможного перемещения лисы.

foxBounds = determineBounds(foxPos);

(2) // Если под лисой пусто, она начинает падать.

if ((foxBounds.bottom > 0) and (!falling)) falling = true;

(3)// Падение.

if (falling) checkFall();

(4) // Если нажата левая стрелка, то движемся влево, если там
// нет препятствия.

if (Key.isDownfKey.LEFT)) {
if (foxSpeed < foxBounds.left) {
foxPos.x -= foxSpeed;}
if (foxPos.x < 0) foxPos.x = 0;
fox._xscale = 25;
moving = true;
// Если нажата правая стрелка, то движемся вправо, если там
// нет препятствия.
} else if (Key.isDown(Key.RIGHT)) {
if (foxSpeed < foxBounds.right) {
foxPos.x += foxSpeed;}
if (foxPos.x > worldEnd) foxPos.x = worldEnd;
fox._xscale = -25;
moving = true;
// Если не движемся.
} else {
moving = false;}

(5)// Если стоим на поверхности и нажат пробел - прыгаем,

if (Key.isDown(Key.SPACE) and (!falling)) {
fallSpeed = jumpPower;
// Прыжок = падение вверх
falling = true;
if (Jmoving) {
// Используем анимацию прыжка только
// если лиса не идет.
fox.gotoAndPlay("jump");}}

(6)// Если идет и не падает, то анимируем ходьбу,

if (moving and !falling) { fox.nextFrame();
// Если не идет или падает - кадр со стоящей лисой.
} else if (imoving and !falling) {
fox.gotoAndStop(1);
}

(7)// Позиция лисы по вертикали.

fox._y = floor - foxPos.у;
// Активируем кролика.
moveBunnies ();
// Перерисовываем все объекты в соответствии с новой
// позицией.
drawObjects();
// Проверяем,не съеден ли орех.
getAcorns();}

Функция determineBounds выглядит сложно, но на самом деле она довольно простая. Вначале мы предполагам, что пространство слева, справа и сверху вокруг лисы пустое на расстоянии 1000 пикселов. Также полагаем, что нет пустого пространства под лисой. Вертикальную позицию лисы мы храним в свойстве pos.у.
Далее следует цикл по всем объектам типа box. Вычисляется расстояние от лисы до блока и записывается в переменные dx и dy.
Если блок занимает то же положение по вертикали, что и лиса (другими словами - если он на том же расстоянии от земли), то функция проверяет, находится ли он справа или слева. Далее проверяется, если расстояние до блока справа (слева) меньше текущего значения bounds.right (.left), то значение bounds.right (.left) переопределяется. Аналогично проверяются вертикальные границы.
После того как все блоки были проверены, объект bounds содержит горизонтальные и вертикальные границы для лисы в текущем положении. Например, если bounds.left равно 20, то ближайший к лисе блок справа находится на расстоянии 20 пикселов.
Функция determineBounds написана в достаточно общем виде, чтобы ее можно было использовать как для лисы, так и для кроликов. В качестве аргумента pos функции можно передать как объект foxPos, так и элемент массива objects, например кролика.

function determineBounds(pos) {
// Определяем границы перемещения.
var bounds = {left: 1000 , right: 1000, top: 1000 ,bottom:pos.у};
// Цикл по всем объектам.
for(var i=0;i // Рассматриваем только блоки,
if (objects[i].type == "box") {
var dx = objects[i].x - pos.x;
var dy = objects[i].y - pos.y;
// Если блок в той же вертикальной позиции,
if ((dy >= 0) and (dy <= 50)) {
// Определяем, является ли ближайшим левый блок,
if ((dx+50 <= 0) and (Math.abs(dx+50) < bounds.left)) {
bounds.left = Math.abs(dx+50);
// Определяем, является ли ближайшим правый блок.
} else if ((dx >= 0) and (dx < bounds.right)) {
bounds. right = dx-50;
}} // Блок в той же горизонтальной позиции,
if ((dx >= -50) and (dx <= 50)) {
// Определяем, является ли ближайшим нижний блок.
if ((dy+50 <= 0) and (Math.abs(dy+50) <= bounds.bottom)) {
bounds.bottom = Math.abs(dy+50);
// Определяем, является ли ближайшим верхний блок.
} else if ((dy-50 >= 0) and (dy-50 < bounds.top)) {
bounds.top = dy-50;
}}}}
return(bounds);}

Если лиса находится в воздухе, то независимо от того, прыгает ли она вверх или падает вниз, это состояние рассматривается как падение. Функция CheckFall следит за вертикальным перемещением лисы. Когда игрок прыгает, переменная fallSpeed принимает значение jumpPower, которое равно 60. Таким образом, лиса пытается переместиться на 60 пикселов по вертикали. В каждом кадре, в котором происходит процесс "прыжок/падение", переменная fallSpeed уменьшается на 10, что создает эффект силы тяжести. В конечном счете значение fallSpeed обнулится в верхней точке прыжка и начнет уменьшаться, а лиса устремится вниз.
Если значения скорости падения еще недостаточно, чтобы лиса достигла земли, то падение продолжается. Но как только уровень земли достигнут или пройден, падение прекращается, и вертикальная позиция лисы устанавливается равной положению земли.
Функция checkFall также обращается к свойству foxBound.top, чтобы проверить, нет ли блока над лисой. Если лиса упирается в верхнюю границу возможного движения, то импульс, направленный вверх, пропадает, переменная falispeed обнуляется. Процесс "прыжок/падение" продолжается, но является теперь только падением.

function checkFall() {
// Учитываем силу тяжести.
fallSpeed -= 10;
// Проверяем, есть ли место для падения,
if (fallSpeed > -foxBounds.bottom) {
foxPos.y += fallSpeed;
// Прекращаем падение и устанавливаем лису на уровень земли.
} else {
foxPos.y -= foxBounds.bottom;
fallSpeed = 0;
falling = false;
fox.gotoAndStop(1);
}
// Смотрим, не упирается ли лиса в верхний блок.
if (foxPos.y > foxBounds.top) {
foxPos.y = foxBounds.top;
fallSpeed = 0;
}}

Функция drawobjects создает эффект движения лисы. Она перерисовывает все объекты в соответствии с положением лисы.

function drawObjects() {
// Цикл по всем объектам.
for(var i=0;i // Устанавливаем горизонтальную позицию в соответствие
// с положением лисы.
_root["object "+i]._x = х = 275 + objects[i].x -foxPos. х;
// Устанавливаем горизонтальную позицию в соответствие
//со значением floor.
_root["object "+i]._y = floor - objects[i].у;
}}

Функция getAcorns просматривает объекты в поисках орехов. Каждый орех проверяется, достаточно ли близко он находится к лисе, чтобы она могла его взять.
Если орех съеден, его свойство type устанавливается в значении used. Этот орех теперь игнорируется всеми функциями, так как они не проверяют объекты типа used.

function getAcorns() {
// Просматриваем все объекты,
for(var i=objects.length-1;i>=0;i--) {
if (objects[i].type == "acorn") {
// Если ближе 30 пикселов - хватаем орех,
if (distance(_root["object "+i],fox) < 30) {
_root["object "+i].play();
objects[i].type = "used";
score += 100;
}}}}

Функция getAcorns вызывает функцию distanse для определения расстояния между лисой и орехом. Эта же функция будет в дальнейшем использоваться функцией moveBunnies.

// Функция для определения расстояния между клипами,
function distance(mc1,mc2) {
d = Math.sqrt(Math.powfmcl._x-mc2._x,2)+ Math.powfmcl._y-mc2._y,2));
return d;
}

Все кролики контролируются функцией moveBunnies. Нет смысла следить за кроликами вне экрана, поэтому перемешаются только кролики, находящиеся не далее чем 275 пикселов от лисы.
Перед перемещением кролика вызывается функция determineBounds для определения границ его возможного перемещения. Если границы позволяют, кролик начинает передвигаться в сторону позиции игрока. Если лиса оказывается достаточно бестолковой, чтобы подобраться близко к кролику, игра заканчивается.

function moveBunnies() {
// Просматриваем все объекты в поисках кроликов.
for(var i=objects.length-l;i>=0;i-) {
if (objects[i].type == "bunny") {
// Перемещаем только видимых кроликов.
if (Math.abs(objects[i].x-foxPos.x) < 275) {
// Движемся в сторону лисы,
if (foxPos.x < objects[i].x) {
var dx = -bunnySpeed;
} else if (foxPos.x > objects[i].x) {
var dx = bunnySpeed;
// Определяем границы.
bunnyBounds = determineBounds(objects[i]);
// Движемся только в пределах этих границ.
if ((dx < 0) and (bunnyBounds.left > Math.abs(dx))) {
objects[i].x += dx;
} else if ((dx > 0) and (bunnyBounds.right > Math.abs(dx))) {
objects[i].x += dx;}
//He подобрался ли кролик достаточно близко к лисе.
if (distance(_root["object "+i],fox) < 30) {
_root.onEnterFrame = undefined;
trace("got ya");
}}}}}

К сведению

Клип с лисой содержит три части: "stand", "run" и "jump". В первом кадре содержится команда stop(). В последнем кадре анимации "run" содержится команда gotoAndPlay ("run"). Благодаря этому движение зацикливается.
Клип "acorn" также содержит команду stop О в первом кадре.
Еще один важный момент - это установка центра координат клипов. Он расположен внизу и отцентрирован по горизонтали. На рис 16.9 изображен клип "box" и его центр.

Рис 16.9 Во всех клипах центр находится внизу точно посередине

Другие возможности

Эта игра демонстрирует лишь простейшие возможности платформенного скроллера. Она настолько проста, что даже не имеет завершения. Существует несколько возможностей оформить конец игры.
Во первых, вы можете достигать крайней правой точки для окончания игры или уровня. Пространство этой игры относительно невелико, но если сделать его в несколько раз больше, то прохождение до границы уже будет некоторым достижением.
В другом варианте игра оканчивается, когда игрок собрал все орехи. Опять же вы можете сделать пространство игры больше.
Большинство платформенных скроллеров предлагают также избавляться от разных отрицательных героев. Обычно для этого достаточно прыгнуть сверху на такого плохого парня. В существующем варианте игры, если вы запрыгнете на кролика, игра окончится точно так же, как если бы вы пересеклись с ним по горизонтали. Но если вы сможете определить, что лиса падает сверху в момент столкновения с кроликом, то получите сражающуюся лису, которая будет получать дополнительные очки за побежденных кроликов.
Платформенные скроллеры могут быть очень сложными. Они запросто могут включать сотни страниц кода, добавляющих все больше и больше характеристик в игру. У вас же сейчас реализованы лишь самые основные моменты. Используя навыки предыдущих глав, вы вполне можете сколько угодно надстраивать эту игру.