Текст книги "Создание игр для мобильных телефонов"
Автор книги: Майкл Моррисон
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 16 (всего у книги 35 страниц)
Приложение Tile Studio очень похоже на Mappy, но обладает большими возможностями. Однако расширение возможностей приводит и к повышению сложности. Не поймите меня неправильно, Tile Studio – очень полезная программа, и вам, вероятно, она может показаться более универсальной, чем Mappy. Но чтобы начать работу над картами для мобильных игр, лучше применять Mappy, поскольку она проще в использовании. Поэтому я не буду тратить время на объяснение основ работы в Tile Studio. Вместо этого посмотрите на рис. 10.8, на нем показана карта, которую я создал с помощью Tile Studio.
Рис. 10.8. Интерфейс Tile Studio аналогичен интерфейсу Mappy
В копилку Игрока
Вы можете загрузить Tile Studio с этой страницы в Internet: http://tilestudio.sourceforge.net/.
Как видно из приведенного рисунка (рис. 10.8), в Tile Studio в нижней части экрана вы можете выбрать нужный фрагмент и разместить его на карте. В Studio есть ряд специальных возможностей, которые вы не найдете в Mappy, но, может быть, вам они не понадобятся для работы. Я советую вам поработать с каждым из упомянутых приложений и решить, какое из них вам подходит более. Я могу с уверенностью сказать, что подобное программное обеспечение ускоряет и облегчает процесс разработки мобильных карт.
Раньше шла речь о Mappy, программе для создания карт, и я показал вам код, созданный таким приложением. Я не уточняю, что этот код означает, но вы можете догадаться, что цифры, разделенные запятыми, – это индексы слоев. Существует несколько способов запрограммировать карту. Самый простой способ сделать это – использовать массив целых чисел для хранения индексов. Несмотря на то что массив – это ряд чисел, в коде вы можете разделить строки и столбцы.
Карта, созданная Mappy, уже разделена на строки, а вот со столбцами сложнее, поскольку числа не выровнены. Если вы выровняете числа и заключите их в массив, то получится следующий Java-код:
int[] layerMap = {
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 22, 3,
3, 18, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 20, 3,
3, 18, 2, 2, 2, 5, 15, 15, 15, 15, 15, 15, 6, 2, 20, 3,
3, 18, 2, 2, 2, 7, 10, 1, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 14, 1, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 7, 10, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 2, 14, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 2, 14, 1, 9, 10, 1, 16, 2, 20, 3,
3, 18, 2, 5, 15, 6, 2, 14, 1, 11, 12, 1, 16, 2, 20, 3,
3, 18, 2, 14, 1, 16, 2, 7, 13, 13, 13, 13, 8, 2, 20, 3,
3, 18, 2, 7, 13, 8, 2, 2, 2, 2, 2, 2, 2, 2, 20, 3,
3, 18, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 20, 3,
3, 23, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 24, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3.
};
Теперь в вашем распоряжении есть массив индексов, который можно использовать для создания карты в мобильной игре. Вне зависимости от того, создаете ли вы карту, рисуя ее карандашом на листе бумаги, или применяя Mappy, Tile Studio или другое программное обеспечение, в результате вы должны получить массив целых чисел, который представляет собой таблицу индексов. Если вы до сих пор испытываете трудности с пониманием назначения массива, посмотрите на рис. 10.9.
Рис. 10.9. Индексы слоев на картинке проставляются автоматически слева направо и сверху вниз, начиная с 1
Вне зависимости от того, сколько элементов хранится в изображении для замощенного слоя, они индексируются так, что верхний левый элемент имеет номер 1, затем нумерация продолжается вправо и вниз. Теперь, если вы сравните индексы элементов на рис. 10.9 с форматированным кодом, который видели ранее, то поймете, как была сформирована карта, показанная на рис. 10.10.
Рис. 10.10. Карту можно восстановить, если каждому элементу кода поставить в соответствие нужный фрагмент карты
Этот рисунок должен раскрыть тайну индексов и то, как они используются в целочисленном массиве для построения замощенного слоя карты. Вы вернетесь к этой карте чуть позже, когда будете работать над мидлетом Wanderer. Теперь, когда вы представляете, как создавать карты, можно перейти к знакомству с классом TiledLayer и его применением для создания замощенных слоев.
Работа с классом TiledLayerЗамощенные слои поддерживаются MIDP 2.0 API, для этого используется класс TiledLayer. Он облегчает создание и применение таких слоев. Каждый замощенный слой – это объект, с которым ассоциировано изображение, определяющее набор элементов, которые используются для создания замощенного слоя карты. Каждый замощенный слой имеет карту, содержащую индексы, которые означают определенный фрагмент изображения. Поскольку родительским классом для TiledLayer является класс Layer, с объектами этого класса можно работать так же, как со спрайтами. Иначе говоря, вы можете изменять положение замощенного слоя, запрашивать его размер и текущее положение, выводить на экран и управлять видимостью. Для этого требуется лишь несколько вызовов методов.
При создании замощенного слоя вы задаете ширину и высоту в количестве элементов, определяете изображение, содержащее необходимые элементы, а также указываете высоту и ширину фрагментов. Эта информация передается в конструктор TiledLayer(). Ниже приведен код, создающий замощенный слой, представляющий собой трассу (рис. 10.2):
Рис. 10.2. Когда размер замощенного слоя больше размера экрана, то в любой момент времени отображается лишь определенная часть
TiledLayer backgroundLayer;
try {
backgroundLayer = new TiledLayer(5, 4, Image.createImage("/RaceTrack.png"),
100, 100);
}
catch (IOEXception ioe) {
System.err.printIn("Failed loading images!");
}
Первые два параметра в вызове TiledLayer() определяют число строк и столбцов в замощенном слое соответственно, в данном случае – это 5 и 4. Третий параметр – это объект Image, который представляет собой гипотетический перечень элементов слоя, показанных на рис. 10.1. Оставшиеся два параметра – это ширина и высота одного фрагмента, в нашем случае элементы – это квадраты со стороной 100 пикселей.
После того как создан объект TiledLayer, устанавливается его карта, для чего ячейки заполняются нужными индексами. Если вы посмотрите на рис. 10.1, то заметите, что каждому фрагменту присвоен уникальный номер. Эти номера – индексы в перечне элементов изображения. Индексы всегда начинаются с 1 и увеличиваются. Индекс 0 – это специальный индекс, который определяет отсутствие фрагмента. Иначе говоря, когда вы задаете элемент с индексом 0, то он будет прозрачным.
В копилку Игрока
Перед тем как карта слоя задана, все ячейки содержат индекс 0, что означает, в начале замощенный слой прозрачен.
Используя разметку трассы, представленную на рис. 10.1, карту слоя можно задать в виде целочисленного массива так:
int[] layerMap = {
1, 3, 3, 3, 2,
6, 7, 7, 7, 6,
6, 7, 1, 3, 5,
4, 3, 5, 7, 7
};
Все, что необходимо сделать, чтобы представить карту, – это взглянуть на индексы элементов в массиве. Чтобы облегчить задачу, просто нарисуйте карту на листе бумаги или в программе, например, Mappy или Tile Studio, речь о которых шла выше. Массив в предыдущем элементе кода – это одномерный массив, но он отформатирован так, что вы можете представить отдельные ячейки.
К сожалению, конструктору TiledLayer нельзя передать массив. Вы должны установить в каждой ячейке замощенного слоя массив, для чего вызвать несколько раз метод setCell(). Ниже приведен цикл, выполняющий это:
for (int i = 0; i < layerMap.length; i++) {
int column = i % 5; //Число 5 означает количество столбцов, а 4 – количество строк.
int row = (i – column) / 4;
backgroundLayer.setCell(column, row, layerMap(i));
};
Этот код проходит по всем ячейкам замощенного слоя и присваивает нужный индекс. Этот код можно с легкостью приспособить для замощенного слоя любого размера, для чего необходимо изменить число столбцов (5) и строк (4) во второй и третьей строках кода соответственно.
Теперь вы знаете, что создать замощенный слой с помощью класса TiledLayer очень просто, особенно если вы знаете, как создать карту, состоящую из индексов. Указать положение слоя также не представляет сложности:
backgroundImage.setPosition(0, 0);
Этот код просто устанавливает замощенный слой в начало координат игрового экрана: верхний левый угол слоя расположен в верхнем левом углу экрана телефона. Предположив, что размер слоя больше размера экрана, нижний правый угол замощенного слоя невидим на дисплее. Если, обратившись к документации MIDP API, вы будете искать метод setPosition(), вы увидите, что он не указан среди методов класса TiledLayer. Это потому, что данный класс – производный от класса Layer. Другой метод, наследованный от класса Layer, paint(), отвечает за вывод замощенного слоя. Ниже приведен код, рисующий замощенный слой:
backgroundLayer.paint(g);
Из приведенного кода видно, как мало усилий необходимо затратить, чтобы вывести замощенный слой после того, как он был создан.
Создание программы WandererОставшаяся часть главы посвящена разработке мидлета Wanderer, который представляет собой приключенческий симулятор, где вы управляете героем, перемещающимся по карте. Хотя с технической точки зрения Wanderer – не игра, этот мидлет можно превратить в игру, затратив минимум усилий. Идея Wanderer заключается в том, что здесь используется карта большего размера по сравнению с размером дисплея. Герой остается в центре экрана, потому что перемещается.
Неудивительно, что карта в Wanderer создана, как замощенный слой. В этом мидлете используются два различных объекта слоя: фоновый замощенный слой и спрайт героя. Пожалуйста, посмотрите на рис. 10.9 и 10.10, а также на код карты, чтобы представить карту, используемую в Wanderer. На ней островок земли окружен водой. В мидлете Wanderer следует проверять, что спрайт героя перемещается только по земле, потому что перемещения по воде и скалам запрещены.
Большая часть мидлета Wanderer уделена созданию и управлению замощенным слоем. На самом деле, поскольку замощенный слой перемещается под неподвижным спрайтом, вы можете не трогать спрайт в мидлете, оставив его неподвижным, а не создавать анимацию, имитирующую походку.
Код программы Wanderer начинается с объявления переменных класса, необходимых для реализации замощенного слоя и его перемещения. Эти переменные – объекты классов TiledLayer и Sprite, последний необходим для имитации героя. Ниже приведен код, объявляющий эти переменные:
private TiledLayer backgroundLayer;
private Sprite personSprite;
Переменная backgroundLayer управляет замощенным слоем в мидлете, в то время как переменная personSprite отвечает за героя.
Эти переменные инициализируются в методе start() класса WCanvas, в котором создаются замощенный слой и спрайт. Вот код, создающий фоновый замощенный слой:
try {
backgroundLayer = new TiledLayer(16, 16,
Image.createImage("/Background.png"), 48, 48);
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
Если вы вспомните, о чем шла речь в начале главы, конструктор TiledLayer() требует задать число строк и столбцов в замощенном слое, а также изображение, содержащее отдельные элементы, и размеры одного элемента. Эта информация передается конструктору в приведенном выше коде. Замощенный слой состоит из 16 строк и столбцов, его элементы имеют размер 48х48 пикселей. Кроме того, эти изображения хранятся в файле Background.png (рис. 10.9).
Наиболее важная часть создания замощенного слоя – это определение карты слоя. Для этого вы должны задать массив (или карту), состоящий из индексов, которые определяют вид замощенного слоя. Ранее вы увидели, как с этой задачей может помочь программное обеспечение для создания карт, оно даже создает необходимый код. Ниже приведен массив для инициализации замощенного слоя, вы уже видели его раньше:
int[] layerMap = {
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 22, 3,
3, 18, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 20, 3,
3, 18, 2, 2, 2, 5, 15, 15, 15, 15, 15, 15, 6, 2, 20, 3,
3, 18, 2, 2, 2, 7, 10, 1, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 14, 1, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 7, 10, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 2, 14, 1, 1, 1, 1, 16, 2, 20, 3,
3, 18, 2, 2, 2, 2, 2, 14, 1, 9, 10, 1, 16, 2, 20, 3,
3, 18, 2, 5, 15, 6, 2, 14, 1, 11, 12, 1, 16, 2, 20, 3,
3, 18, 2, 14, 1, 16, 2, 7, 13, 13, 13, 13, 8, 2, 20, 3,
3, 18, 2, 7, 13, 8, 2, 2, 2, 2, 2, 2, 2, 2, 20, 3,
3, 18, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 20, 3,
3, 23, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 24, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
};
Этот массив должен быть вам знаком: вы разрабатывали карту, которую он описывает, в предыдущих разделах. Очевидно, что объявления массива не достаточно для определения замощенного слоя. Чтобы задать слой, вы должны определить значение каждой ячейки, для чего необходимо использовать метод setCell() класса TiledLayer. К счастью, это не так сложно сделать с помощью цикла for:
for (int i = 0; i < layerMap.length; i++) {
int column = i % 16; //Размер карты 16x16
int row = (i – column) / 16;
backgroundLayer.setCell(column, row, layerMap[i]);
}
Наиболее важный момент в этом цикле, на который следует обратить внимание, – это использование числа 16 во второй и третьей строках кода. Это число во второй строке означает количество столбцов, а в третьей – количество строк. Если вы измените размер карты, то вы должны изменить и эти числа в соответствии с изменениями. Самое приятное в этом коде – это то, что весь слой инициализируется всего пятью строками кода.
Совет Разработчику
Вместо того чтобы работать с одномерным массивом и проходить по всем его элементам, вы можете создать двумерный массив и использовать вложенные циклы. Оба подхода справедливы и, вероятно, приблизительно одинаково эффективны. Здесь я рассмотрел лишь случай одномерного массива.
Когда фоновый слой создан и заполнен картой, остается только завершить инициализацию, установив его в начальное положение. Помните, что положение фонового слоя задается относительно начала системы координат, связанной с экраном (верхний левый угол дисплея). Если вы хотите центрировать экран по отношению к карте, инициализируйте замощенный слой отрицательными значениями. Посмотрите на рис. 10.11, который поясняет, почему фоновый слой необходимо инициализировать отрицательными координатами.
Рис. 10.11. Чтобы центрировать игровой экран относительно карты, замощенный слой необходимо инициализировать отрицательными числами
Ниже приведен код, который инициализирует фоновый слой так, что игровой экран оказывается в центре карты:
backgroundLayer.setPosition((getWidth() – backgroundLayer.getWidth()) / 2,
(getHeight() – backgroundLayer.getHeight()) / 2);
А где же отрицательные значения, инициализирующие положение слоя? Поскольку высота и ширина холста меньше, чем высота и ширина фонового слоя, координаты положения слоя, вычисляемые в приведенном коде, будут отрицательными. Поэтому выполнение таких вычислений освобождает вас от необходимости вводить координаты вручную.
Итак, вы установили фоновый слой. Теперь можно сосредоточиться на спрайте героя, он объявляется точно так же, как и любой другой спрайт:
try {
personSprite = new Sprite(Image.createImage("/Person.png"), 20, 24);
personSprite.setPosition((getWidth() – personSprite.getWidth()) / 2, //Спрайт героя располагается в центре экрана
(getHeight() – personSprite.getHeight()) / 2);
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
Спрайт героя состоит из двух фреймов, но вся информация, которая необходима для его создания – это его размер (20 24 пикселя). Конструктор Sprite() очень умен, чтобы понять, что изображение Person.png, размером 20 48 пикселей содержит два фрейма. После того как спрайт создан, он выводится в центре экрана, для чего выполняются несложные вычисления.
Метод update() обрабатывает пользовательский ввод. В этом примере в результате нажатия клавиш перемещается фоновый слой, расположенный под спрайтом персонажа, который остается неподвижным в центре экрана. В листинге 10.1 приведен код метода update().
Листинг 10.1. Метод update() класса WCanvas передвигает карту в соответствии с нажатиями клавиш пользователем
private void update() {
// обработка пользовательского ввода, перемещение фона, имитирующее
ходьбу героя
if (++inputDelay > 2) {
int keyState = getKeyStates();
if ((keyState & LEFT_PRESSED) != 0) {
backgroundLayer.move(12, 0); //Фоновый слой перемещается в ответ на нажатие клавиш
personSprite.nextFrame();
}
else if ((keyState & RIGHT_PRESSED) != 0) {
backgroundLayer.move(-12, 0);
personSprite.nextFrame(); //Спрайт героя – анимационный, он симулирует хождение человека
}
if ((keyState & UP_PRESSED) != 0) {
backgroundLayer.move(0, 12);
personSprite.nextFrame();
}
else if ((keyState & DOWN_PRESSED) != 0) {
backgroundLayer.move(0, -12);
personSprite.nextFrame();
}
checkBackgroundBounds(backgroundLayer); //Этот код гарантирует, что фоновый слой не выйдет за свои границы
// обнулить задержку ввода
inputDelay = 0;
}
}
Метод update() несложный, он перемещает фоновый слой в соответствии с нажатыми клавишами. Единственное, что вас может удивить, – необходимость перемещать замощенный слой в направлении, противоположном направлению перемещения героя. Например, чтобы создать иллюзию того, что герой перемещается влево, фон необходимо переместить вправо. Фреймы спрайта героя сменяют друг друга при каждом движении. Поскольку спрайт состоит из двух фреймов, то они отображаются поочередно.
Почти в конце метода update() производится вызов метода checkBackgroundBounds(), который проверяет, чтобы герой не вышел за границы карты. Этот метод приведен в листинге 10.2.
Листинг 10.2. Метод checkBackgroundBounds() проверяет, чтобы герой не вышел за пределы карты
private void checkBackgroundBounds(TiledLayer background) {
// при необходимости остановить фон
if (background.getX() > -15) //Числа в этом коде аккуратно вычислены так, чтобы герой не вышел за границы замощенного слоя
background.setPosition(-15, background.getY());
else if (background.getX() < -572)
background.setPosition(-572, background.getY());
if (background.getY() > -25)
background.setPosition(background.getX(), -25);
else if (background.getY() < -572)
background.setPosition(background.getX(), -572);
}
Хотя основной целью метода checkBackgroundBounds() является проверка того, чтобы герой не вышел за пределы замощенного слоя, ограничение – чуть более жесткое. Необходимо создать иллюзию того, что спрайт героя не может передвигаться по воде и скалам, поэтому такие перемещения необходимо заблокировать. Числа, которые вы видите в представленном листинге, ограничивают перемещение спрайта героя лишь краем воды и скал.
Последний фрагмент кода мидлета Wanderer, который представляет интерес, – это метод draw(), который отвечает за вывод фонового слоя и спрайта. В листинге 10.3 приведен код этого метода.
Листинг 10.3. Метод draw() выводит фоновый замощенный слой и спрайт героя
private void draw(Graphics g) {
// вывести фоновый слой
backgroundLayer.paint(g); //Чтобы вывести замощенный слой на экран, достаточно одной строки кода
// вывести спрайт героя
personSprite.paint(g);
// вывести содержимое буфера на экран
flushGraphics();
}
В этом коде нет ничего особенного. Объекты backgroundLayer и personSprite вызывают методы paint(), который выводит на экран замощенный слой и спрайт героя.
Поскольку, возможно, позиционирование фонового слоя непросто понять с первого раза, я поясню иначе. Сказав, что следует использовать отрицательные координаты, я, вероятно, ввел вас в заблуждение. Все можно представить по-другому, чтобы вы лучше поняли. Попробуйте вывести на экран текущее положение фонового слоя. Для этого в метод draw() перед вызовом метода flushGraphics() необходимо вставить следующий код:
//вывести текущее положение фонового слоя
String s = "X = " + backgroundLayer.getX() + ", Y = " + backgroundLayer.getY();
g.drawString(s, 0, 0, Graphics.TOP | Graphics.LEFT);
Следует отметить, что такой же подход вы можете использовать для вывода любой необходимой информации. Например, можно отобразить текущую скорость или положение спрайта, который ведет себя не так, как вы предполагали.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.