Редактор уровней
В этой главе речь пойдет о том, как сделать редактор уровней для игр, основанных на массивах. Это игры вроде Breakout, Boulderdash, PacMan или клоны Nibbles, как, например, наша игра Snakin, где этот редактор и используется. Перед тем, как начать чтение, вам следует знать основы о массивах в Java, кое-что о параметрах апплета, и, конечно, скачайте исходный код в конце главы.
Почему нам нужен редактор уровней?
Даже если ответ вам известе, я все же напигу несколько строчек по этому поводу. Чтобы создать редактор уровней и заставить игру работать с любым уровнем, сделанным в нем, гораздо труднее и займет больше времени, чем написание игры с одним или двумя статичными уровнями. Но это имеет немалый смысл, если вы хотите написать игру с множеством разных уровней. Если вы создаете такую игру с редактором уровней, то создание и добавление нового уровня впоследствии займет лишь несколько минут. В обратном случае это будет трудоемко, если вообще возможно. Так что трудная работа в начале проекта окупится вам сторицей! Итак, начнем с реальной проблемы.
Основная идея
Перед тем, как мы найдем решение задачи, где и как определять наши уровни, следует подумать о кое-чем еще. Нужно найти способ, как мы будем представлять уровень в игре.
Представьте поле апплета с сеткой над ним. Мы помещаем элементы, используемые в уровне (стены, камни, враги...) на клетках этой сетки (только один элемент на одну клетку). Это значит, что позиция каждого элемента уровня определена номером строки и столбца той клетки, где он находится. Самый легкий путь представить такую двумерную матрицу в Java - это использовать двумерный массив. Таким образом, каждый уровень состоит из двумерного массива. Этот массив содержит различные элементы уровня, которые мгут меняться от уровня к уровню. Каждый раз, когда мы создаем уровень, мы помещаем разлиные объекты на разные позиции наешего 2D массива. Когда мы хотим вывести уровень на экран, мы используем массив и рисуем каждый объект на тех местах сетки, которые представлены номерами строк и столбцов массива. Используя эту почти изящную идею, сейчас мы попытаемся решить задачу, где и как мы хотим определить наши уровни так, чтобы было возможно заполнить массив уровня позднее.
Где и как определять уровни?
Мы программируем апплеты, так что в основном у нас есть три возможности определения уровня:
1. Во внешнем файле
2. В исходном коде игры
3. На HTML-странице апплета (с помощью параметров апплета)
Первое и второе решения по некоторым причинам хуже третьего. Чтение из внешнего файла возможно, но не слишком легко. Определять различные уровни в исходном коде - хорошее решение, когда игрок не должен иметь возможности видеть его до того, как он достиг его, например, если цель игры - найти выход из лабиринта. Но вы не можете применять это решение, если хотите дать возможность игроку писать уровни самостоятельно. Таким образом, остается только третья альтернатива, и сейчас мы приступим к ее воплощению.
Итак, для создания уровней мы используем параметры апплета. Каждый параметр апплета имеет значение (value) и имя (name). Эти данные мы можем получать с помощью метода
getParameter(имя параметра) в классе апплета. Значение параметра со специфичным именем возвращается в виде строки (string). Параметры могут быть определены между открывающим и закрывающим тегами апплета и выглядят примерно так:
<param name= "name of the parameter" value="value of the parameter">
Итак, мы определились, где будут писаться наши уровни, но пока не знаем, что они будут из себя представлять. Давайте рассмотрим мое решение. Каждый уровень будет состоять из 11 параметров: 3 информационных, содержащих данные об авторе, описание уровня и его имя. Остальные 8 параметров - строки массива, которые представляют наш уровень в примере. Имена параметров всегда будут такими: Level" + "Levelnumber" + "_" + "Id". Id может иметь значения "Author", "Name", "Comment" или "Line" + "Rownumber". Значения строк информационных параметров могут иметь любую длину, значения параметров, определяющих уровень, должны иметь длину в 10 символов. Каждый символ строки представляет один элемент уровня, в нашем случае это камни разного цвета. Пары символы/цвета будут такими: r = red (красный), g = green (зеленый), b = blue (синий), y = yellow (желтый) а другие - ":", которые будут клетками поля, на которых нет никаких элементов. Имена параметров различных уровней будут одинаковыми, за исключением номера уровня. Благодаря этомй структуре имен будет простым делом считывать уровни, используя цикл for или while, начиная отсчет с 1 в параметре "Levels_in_total" (детали см. метод readLevel далее в этой главе). Ниже представлен уровень, который может интерпретироваться редактором уровней:
// Start of the applettag including the normal applet information
<applet code = Main width=300 height=400>
// This line tells the editor how many levels are defined
<param name="Levels_in_total" value="1">
// These lines include the level information parameters
<param name="Level1_Author" value="FBI">
<param name="Level1_Name" value="Test Level 1">
<param name="Level1_Comment" value="My first try">
// This is the "real" level
<param name="Level1_Line0" value="rrrrrrrrrr">
<param name="Level1_Line1" value="bbbbbggggg">
<param name="Level1_Line2" value="r::rrrr::r">
<param name="Level1_Line3" value="yyyyybbbbb">
<param name="Level1_Line4" value="rrr::::rrr">
<param name="Level1_Line5" value="gggggyyyyy">
<param name="Level1_Line6" value="r::rrrr::r">
<param name="Level1_Line7" value="bgybgrybgy">
// End of the applet tag
</applet>
Проектирование классов редактора уровней
А теперь давайте приступим к проектированию классов нашего редактора, который делает возможным считывать и интерпретировать уровни.
1.
LevelReader:
Этот класс будет считывать номер уровня, определенный в параметре
"Levels_in_total", используя метод getParameter(имя параметра). Для каждого html-файла он будет создавать экземпляр класса
Level (см.ниже) и хранить его в массиве экземпляров класса Level. атем метод
readLevels(), который выполняет работу по чтению всех уровней, возвращает этот массив в вызывающий класс.
2.
Level:
Этот класс сохраняет значения информационных строк, где указаны
автор, имя уровня и содержит двумерный массив
(stone_map) с элементами уровня. Как уже говорилось, этот массив сохраняет экземпляры класса Stone в соответсвтии с определением уровня в разных цветах. Каждый камень "знает" свой цвет и положение на сетке уровня. Класс Level также имеет метод, рисующий весь уровень на экране.
3.
Stone:
Этот класс хранит цвет и место расположения камня в области апплета. Позиция (в пикселях) вычисляется в конструкторе класса, используя информацию, в какой колонке и строке массива
stone_map
расположен камень. Экземпляр stone имеет также собственный метод paint для раскрашивания камня в нужный цвет на нужной позиции.
4. Main:
Этот класс содержит массив экземпляров класса level. Уровень может быть выбран из этого массива с использованием клавиш-стрелок, после этого он выводится на экран. Это просто тестовый класс, и он абсолютно не играет роли для редактора уровней.
5. C_LevelEditor:
Чтобы сделать редактор уровней более гибким и при этом не перелопачивать весь исходный код заново, все постоянные значения (количество линий в уровне, количество колонок, размер ячейки сетки...) хранятся в классе
C_LevelEditor. Если вам вдруг захочется сделать больше линий в уровне, вам нужно будет просто поменять значение соответствующей константы. В общем, в классе находятся только константы и ничего больше.
Исходный код наиболее важных методов
Конечно, каждая строчка кода обсуждаться не будет, но о самых важных методах и классах мы поговорим. Это класс LevelReader и его метод readLevels(), а также класс Level. Классы Main, Stone и C_LevelEditor очень просты, и вам не составит труда лично в них разобраться.
LevelReader
В первую очередь на необходимо считать все определенные уровни в наш апплет. Это реализуется в классе LevelReader благодаря методу readLevels(). Метод не представляет собой никаких трудностей и не требует объяснений, за исключением одной специфичной вещи: мы должны установить ссылку на класс апплета в классе LevelReader. Эта ссылка на Component = Applet необходима, чтобы использовать метод getParameter(). Далее представлен код:
import java.util.*;
import java.applet.*;
import java.awt.*;
public class LevelReader
{
-
// Переменные
private int levels_in_total;
// Массив, хранящий все сгенерированные экземпляры класса Level
private Level [] level_array;
// Ссылка на апплет
private Component parent;
public LevelReader (Component parent)
{
-
// Инициализация ссылки
this.parent = parent;
// Узнаем, сколько всего уровней
levels_in_total = Integer.parseInt (((Applet)parent).getParameter (C_LevelEditor.total_levels));
// Инициализация level_array
level_array = new Level [levels_in_total];
/* Этот метод считывает каждый уровень с html-страницы, генерирует экземпляр класса
(для каждого уровня) и хранит их в level_array*/
public Level [] readLevels ()
{
-
for (int i = 1; i <= levels_in_total; i++)
{
-
// Генерируем новый уровень
Level level = new Level ();
// Получаем и устанавливаем информационные параметры
level.setAuthor (((Applet)parent).getParameter ("Level" + i + "_" + C_LevelEditor.author));
level.setName (((Applet)parent).getParameter ("Level" + i + "_" + C_LevelEditor.name));
level.setComment (((Applet)parent).getParameter ("Level" + i + "_" + C_LevelEditor.comment));
// Считываем все линии и сохраняем в уровень
for (int j = 0; j < C_LevelEditor.number_of_lines; j++)
{
-
level.setLine (((Applet)parent).getParameter ("Level" + i + "_Line" + j), j);
// Сохраняем уровень
level_array [i-1] = level;
return level_array;
}
Класс Level
Мы уже можем считывать уровни и сохранять их в экземплярах класса Level, но до сих пор почти ничего не знаем о самом классе Level. Теперь нам нужно создать реальный уровень, главным образом, stone_map, где хранятся различные элементы уровня, в соответствии с тем, как был создан уровень на html-странице. Этот двумерный массив хранит элементы на тех же позициях, как они определены в параметрах апплета: например, символ "r", находящийся на третьей строке и третий по счету, будет выглядеть в апплете как красный прямоугольник в третьем ряду и тертьей колонке. Прежде всего, в этом классе есть методы, получающие и устанавливающие значения информационных параметров. Более интересен метод setLine. Он берет одну строку определения уровня и переводит ее в объекты-камни, сохраняя их в stone_map. Также здесь имеется свой метод paint.
import java.awt.*;
public class Level
{
-
// Переменные
private String author;
private String name;
private String comment;
// Матрица уровня, хранящая объекты-камни
private Stone [ ] [ ] stone_map;
public Level ()
{
-
// Инициализация stone_map, все поля инициализированы нулем
stone_map = new Stone [C_LevelEditor.number_of_lines]
[C_LevelEditor.number_of_cols];
// Метод переводит информацию строки определения уровня в объекты stone (камни)
stone objects
public void setLine (String line, int line_index)
{
-
char [] entrys = line.toCharArray();
// Перевод всех символов в объекты stone
for (int i = 0; i < C_LevelEditor.number_of_cols; i++)
{
-
Stone stone = null;
// Создание красного камня, если символ "r"
if (entrys[i] == 'r')
{
-
stone = new Stone (line_index, i, Color.red);
// Создание аналогичным образом камней других цветов
...
// Если символ неизвестен, камень не создается, и данное поле остается
// равным нулю
else
{
-
// Ничего не делаем
// store stone in array if it is not null
if (stone == null)
{
-
// Ничего не делаем
else
{
-
stone_map [line_index] [i] = stone;
// Получающие и устанавливающие методы для информационных строк
...
// Метод, рисующий уровень
public void paintLevel (Graphics g)
{
-
// Проходим через весь массив и рисуем камни
for (int i = 0; i < stone_map.length; i++)
{
-
for (int j = 0; j < stone_map[i].length; j++)
{
-
Stone stone = stone_map [i][j];
// Рисуем камень, или ничего не делаем (если stone равен нулю)
if (stone == null)
{
-
// Не рисуем ничего
else
{
-
stone.drawStone(g);
// Информация для отображения уровня
g.setColor (Color.yellow);
g.drawString (comment, 50, 250);
g.drawString (name, 50, 270);
g.drawString (author, 50, 290);
Вывод
В этой главе я показал вам один из способов, как создавать уровни в html-коде, как создать транслятор (LevelReader) таких уровней, и метод, представляющий уровень в апплете. Как всегда, есть и много других путей реализации всего этого, и, возможно, есть более хорошие, чем мой. Тем не менее, вы можете использовать показанную технику в апплете любой игры, использующей массивы - любой пользователь сможет сделать свои уровни, элементы, менять размеры поля и т.д. Что ж, надеюсь, что смог оказать вам некоторую помощь, и если вы напишете игру, использующую такой редактор уровней, буду рад, если пришлете ее мне. Урок окончен, а ниже есть ссылки на исходный код и на работающий апплет (не забудьте посмотреть на html-код страницы).
Скачать исходный код апплета
Запустить апплет
Следующая глава:
Скроллинг