Глава 13. Скроллинг
Прежде чем займемся скроллингом, нам нужно ясно понимать, что Flash не так быстр. Flash очень даже медленно обсчитывает большое число графческих элементов. Скроллирование сотен плиток (а это значит пересчет позиций, координат и .т.п.) на экране, потребует пересчета одного и того же 20-30 раз в секунду. Скроллирование множества плиток может привести Flash в состояние ступора. Игра может просто уснуть, как удав. «Что?» вскричите вы «Нет скроллинга? Что значит - еле ползет, как засыпающая улитка?» Можно сделать скроллирование плиток, но будьте осторожны и не берите слишком большие области для этого, не скроллируйте большое количество плиток. Что значит слишком много и слишком большая область, ответы на эти вопросы вы получите с опытом. Напоминаю, Flash игры запускаются в Интернет-browser, одновременно, может быть запущено много разных программ ( офисные приложения, например, не дай бог AutoCAD какой-нибудь), а у игрока может быть не самый мощный компьютер. Тестируйте игры на древних компах и вы почувствуете и увидите, что такое медленно. Улыбаемся, сейчас вылетит птичка! Снимаю. Итак, начнем скроллирование.
ТеорияНа левой картинке игра без скроллинга. Герой движется направо, а все вокруг стоит недвижимо. Теперь, правая картинка, здесь скролированная игра. Герой также движется направо, но теперь сдвигается взгляд на карту. Т.е. Герой стоит на месте, а все вокруг него движется влево. 
В теории все легко: когда Герой имеет возможность двигаться, начнем двигать все (кроме него) в обратном направлении. Но тут начинаются странности с направлениями движения. Чтобы их устранить, сделаем так, первое - все двигаем в противоположном направлении от Героя (вместе с ним), второе - Героя двигаем в правильном направлении. Пример. Игра скроллируется. Герой движется на 10 пикселов вправо. Первое, перемещаем все плитки (включая Героя) на 10 пикселов влево и, второе, перемещаем Героя на 10 пикселов вправо. В конце расчетов, Герой остается на месте, а остальные плитки сдвигаются влево. Одно из решений для скроллирования плиток, разместить все плитки на экране, но показывать только малую часть из них, а затем их можно двигать. (Т.е. испльзуется неподвижная рамка, через которую виден мир. Мир при этом может двигаться. Другой вариант, мир неподвижен, двигается рамка.) Игра может значительно замедлится, т.к. для невидимых плиток, все одно, нужно проводить вычисления. Другое решение, удаляем плитки не попадающие в видимую область и присоединяем их, как только они станут видимыми. Это лучше, но Flash потратит много времени на удаление/присоединение клипов плиток. Наша последняя надежда, размещаем только видимые плитки на экране и, когда они не видимы, переименуем их, используя один и тот же клип плитки, и практически их можно не перемещать (все одно изображение все тоже самое). Этот способ скроллинга называется -"gotoAndStop" . 
Посмотрите на картинку, когда плитка справа не видима, перемещаем все плитки влево. Мы переименуем клип, т.к. все наши клипы имеют имя "t_3_4", которое говорит о позиции плитки y=3, x=4. Плитка в новой позиции возможно показывается в другом кадре, вот почему нам нужно правильно перейти на нужный кадр. Поэтому и способ скроллинга называется "gotoAndStop". Подготовка к скроллингуВ большинстве скроллинговых игр, Герой всегда остается в центре экрана (видимой области). 
Количество плиток слева от Героя равно количеству плиток справа от него. Число плиток всегда нечетно 3,5,7,9,11 и т.д. (Сомневаюсь, однако. Возможны варианты. При ширине карты с четным количеством плиток, центр располагается между плитками. Значит надо специально брать ширину карты с нечетным количеством плиток. Аналогично с высотой. ) Аналогично для плиток сверху и снизу. Теперь, добавим свойств, связанных с видимой областью игры, к основному объекту: game = {tileW:30, tileH:30, currentMap:1, visx:7, visy:5, centerx:120, centery:90};
Свойства visx/ visу показывают видимую область карты. Свойства centerx/centery содержат координаты центральной точки нашего экрана карты. Когда скроллируем карту, мы должны уметь показывать плитки еще не объявленные на карте. Так, для Героя, счаствливо топающего налево, пока он не остановился на левом краю, нам надо показать еще несколько плиток рядом с ним. Для этих плиток создадим новый тип объектов плиток без картинки, только для показа (это легко сделать во Flash). В клипе плитки tile (в библиотеке) создадим пустой ключевой кадр на 20 кадре. Добавим код для объявления этого типа плиток: // Плитка «пустышка» тип 4, проходная, показывается кадром - 20 game.Tile4 = function () { }; game.Tile4.prototype.walkable = false; game.Tile4.prototype.frame = 20;
Возможно потребуется открыть частично некоторые плитки в видимой области. Для этого создадим новый клип с именем для связки "frame", клип будет иметь отверстие в середине, и через это отверстие плитки и будут показываться. Строим сдвигающийся мирНачнем с изменения функции buildMap: function buildMap (map) { _root.attachMovie("empty", "tiles", 1); // Запомним количество плиток до центра game.halfvisx=int(game.visx/2); game.halfvisy=int(game.visy/2);
Вычисляем значения двух новых свойств - halfvisx / halfvisу объекта game они хранят иинформацию о числе плиток по Х и У до края видимой области. Когда общее число плиток по Х равно 5, то свойство будет равно 2, т.е. считаем, что от Героя вправо еще есть 2 плитки и влево - 2 плитки, а 1 плитка зарезервирована для Героя. Добавим еще чуть ниже: game.clip = _root.tiles; // Вычисляем координаты Х и У для клипа Героя game.clip._x = game.centerx-(char.xtile*game.tileW)-game.tileW/2; game.clip._y = game.centery-(char.ytile*game.tileH)-game.tileH/2;
Размещаем клипы всех плиток и Героя определяется двумя точками: центральная точка задается свойствами centerx/centery и точка Героя, заданная свойствами xtile/ytile. Когда Герой размещен в точке с координатами по Х - (char.xtile*game.tileW)+game.tileW/2, то клипы плиток должны двигаться в направлении противоположном движению Героя, вот почему мы вычитаем число из координат центральной точки. // Здесь несколько изменился цикл по У и Х, с тем чтобы разместить // плитки видимой области вокруг Героя for (var y = char.ytile-game.halfvisy; y<=char.ytile+game.halfvisy+1; ++y) { for (var x = char.xtile-game.halfvisx; x<=char.xtile+game.halfvisx+1; ++x) { var name = "t_"+y+"_"+x; if(y>=0 and x>=0 and y<=map.length-1 and x<=map[0].length-1){ game[name] = new game["Tile"+map[y][x]](); }else{ game[name] = new game.Tile4(); }
game.clip.attachMovie("tile", name, 1+y*100+x*2); game.clip[name]._x = (x*game.tileW); game.clip[name]._y = (y*game.tileH); game.clip[name].gotoAndStop(game[name].frame); }
}
В цикле создаем все видимые объекты плиток и присоединяем клипы этих плиток. Как видим, счетчик цикла начинается не с 0, а с ytile-halfvisy. Это значит что мы обойдем не весь массив карты, а только часть его - ytile+halfvisy+1. Если плитка видима, то формируется объект плитки и все такое. Если плитка за пределами видимой области, то создается пустая плитка на основе шаблона tile4. Для обеспечения бесшовного соединения скроллированной карты, используем по одному дополнительному ряду плиток справа и снизу. Эти плитки для обеспечения показа части плиток при сдвиге карты. _root.attachMovie("frame", "frame", 100);
Последняя строка присоединяет кадры покрытия невидимой области. // Блок кода для Героя char.clip = game.clip.char; // Вычисляем позицию Героя в пикселах char.x = (char.xtile*game.tileW)+game.tileW/2; char.y = (char.ytile*game.tileH)+game.tileH/2; char.width = char.clip._width/2; char.height = char.clip._height/2; // Запомним Х и У в свойствах клипа для показа char.clip._x = char.x; char.clip._y = char.y; // Переходим к кадру показа клипа Героя char.clip.gotoAndStop(char.frame); // Запомним координаты Героя char.xstep=char.x; char.ystep=char.y;
Герой создается аналогичным способом. Различия в двух новых свойствах - , которые будут использоваться позднее при проверке количества перемещений строк и столбцов плиток в противоположном направлении. Двигай! Двигай!После установки параметров видимой области карты, нужно актуализировать скроллинг карты. В функции moveChar после вычисления ob.xtile/ob.ytile для Героя, добавим строки скроллинга карты: // Запомним координаты клипа Героя game.clip._x = game.centerx-ob.x; game.clip._y = game.centery-ob.y; // Проверим (Объект пошел ВЛЕВО, значит плитки надо сдвигать ВПРАВО)? if(ob.xstep<ob.x-game.tileW){ // ДА Вычисляем позиции новую и старые для сдвига на плитку ВПРАВО var xtile = Math.floor(ob.xstep/game.tileW) + 1; var xnew=xtile+game.halfvisx+1; var xold=xtile-game.halfvisx-1; // Запускаем Цикл (по плиткам видимой области) for (var i = ob.ytile-game.halfvisy-1; i<=ob.ytile+game.halfvisy+1; ++i) { // Изменить плитку со старой позицией на новую позицию changeTile (xold, i, xnew, i, _root["myMap"+game.currentMap]); }
ob.xstep=ob.xstep+game.tileW; // НЕТ Проверим (Объект пошел ВПРАВО, значит плитки сдвигать ВЛЕВО)? } else if(ob.xstep>ob.x){ // ДА Вычисляем позицию по Х, старую и новую позиции плиток при сдвиге var xtile = Math.floor(ob.xstep/game.tileW); var xold=xtile+game.halfvisx+1; var xnew=xtile-game.halfvisx-1; // Запускаем Цикл (по плиткам видимой области) for (var i = ob.ytile-game.halfvisy-1; i<=ob.ytile+game.halfvisy+1; ++i) { // Изменить плитку со старой позицицией на новую changeTile (xold, i, xnew, i, _root["myMap"+game.currentMap]); }
ob.xstep=ob.xstep-game.tileW; }
// Проверим (Объект пошел ВВЕРХ, значит плитки надо сдвигать ВНИЗ)? if(ob.ystep<ob.y-game.tileH){ // ДА - Вычислим позиции старую и новую var ytile = Math.floor(ob.ystep/game.tileH)+1; var ynew=ytile+game.halfvisy+1; var yold=ytile-game.halfvisy-1; // Запускаем Цикл (по плиткам видимой области) for (var i = ob.xtile-game.halfvisx-1; i<=ob.xtile+game.halfvisx+1; ++i) { // Меняем позиции плитки со старой на новую changeTile (i, yold, i, ynew, _root["myMap"+game.currentMap]); }
ob.ystep=ob.ystep+game.tileH; // НЕТ Проверим (Объект пошел ВНИЗ, значит плитки надо сдвигать ВВЕРХ)? } else if(ob.ystep>ob.y){ // Вычислим позиции старую и новую var ytile = Math.floor(ob.ystep/game.tileH); var yold=ytile+game.halfvisy+1; var ynew=ytile-game.halfvisy-1; // Запускаем Цикл (по видимым плиткам) for (var i = ob.xtile-game.halfvisx-1; i<=ob.xtile+game.halfvisx+1; ++i) { // Меняем позиции плиток со старой на новую changeTile (i, yold, i, ynew, _root["myMap"+game.currentMap]); }
ob.ystep=ob.ystep-game.tileH; }
return (true);
Первое, перемещаем клип , хранящий все плитки и Героя в новую позицию основанную на центральной точке и координат Героя. Затем 4 одинаковых блока кода, каждый по своему направлению. Когда Герой переместится больше чем на размер плитки, в цикле вызываем функцию changeTile для коррекции значений переменных. Когда цикл закончится, плитки будут перемещены, переименованы и изменены, обновляем свойства ystep/xstep. Теперь функция changeTile : //**************** // Функция: changeTile // Назначение: Перемещение плиток при скроллинге // Параметры: // xold - прежние координаты точки // yold - // xnew - новые координаты точки // ynew - // map - ссылка на массив текущей карты // Возвращает: // // **************************** function changeTile (xold, yold, xnew, ynew, map) { // Формируем имя плитки по старишному var nameold = "t_"+yold+"_"+xold; // Формируем имя плитки по новошнему var namenew = "t_"+ynew+"_"+xnew; // Проверим (Новошние координаты не вылезают за массив карты)? if(ynew>=0 and xnew>=0 and ynew<=map.length-1 and xnew<=map[0].length-1){ // ДА формируем новую плитку game[namenew] = new game["Tile"+map[ynew][xnew]](); // Запишем новое имя в старешнее место game.clip[nameold]._name = namenew; // Для новошней плитки переходим к нужному кадру клипа game.clip[namenew].gotoAndStop(game[namenew].frame); // Вычисляем координаты клипа game.clip[namenew]._x = (xnew*game.tileW); game.clip[namenew]._y = (ynew*game.tileH); }else{ // НЕТ Формируем пустышку game[namenew] = new game.Tile4(); game.clip[nameold]._name = namenew; game.clip[namenew].gotoAndStop(game[namenew].frame); }
}
Итак, мы получим 2 старых координаты и 2 новых для нашей плитки. Чтобы проверить нахождение плитки в массиве карты, мы обращаемся к карте. Переменная - nameold , будет хранить старое название, а переменная namenew будет содержать новое название. Проверяем плитка в видимой области. Если видима, то создаем новый объект плитки с новым именем и сохраняем его имя в существующем клипе: game.clip[nameold]._name = namenew;
Тем самым, переименовывая клип. Если же плитка попадает в область не видимости, то новый клип пуст, нам не нужно размещать его в новые координаты, достаточно и старых координат. Можно загрузить исходный код отсюда. Исправление ошибок. 29.07.2004. Скроллинг работает не правильно со скоростями больше половинной высоты плиток. Благодарности Крису (Chris) за обнаруженную ошибку и предложенное решение. В функции moveChar для проверки строк/столбцов, которые будут перемещены, нужно добавить строки вычисления переменных xtile/ytile и не у старевших свойств (not old) ob.xtile/ob.ytile . Есть вопросы - пишите мне на semikin@dionaholding.ru Читаем дальше - Глава 14. Больше скроллинга Возвращаемся на Начало учебника
|