Генерация и решение лабиринта с помощью метода поиска в глубину по графу.

  • Алгоритмы
  • В этой статье речь пойдет о самом простом в реализации алгоритме генерации «идеального» лабиринта и его применении для поиска пути.

    Мы рассмотрим алгоритм, основанный на бэктрекинге, позволяющий создавать лабиринты без циклов, имеющие единственный путь между двумя точками. Алгоритм не самый быстрый, довольно требователен к ресурсам, по сравнению с или Крускала, но очень прост в реализации и позволяет создавать ветвистые лабиринты с очень длинными тупиковыми ответвлениями.

    Заинтересовавшихся - прошу под кат.

    В русскоязычном интернете очень мало информации по алгоритмам генерации лабиринтов, что и стало причиной для написания этой статьи.
    Примеры кода на языке Си, а также полный исходный код проекта на GitHub доступны под лицензией GNU GPLv3.
    Ссылки на англоязычные ресурсы и проект вы найдете в конце статьи.

    Описание алгоритма
    Замечание: предполагается, что изначально у каждой клетки есть стенки со всех четырех сторон, которые отделяют ее от соседних клеток.


    2. Пока есть непосещенные клетки



        3. Уберите стенку между текущей клеткой и выбранной
        4. Сделайте выбранную клетку текущей и отметьте ее как посещенную.
      2. Иначе если стек не пуст

        2. Сделайте ее текущей
      3. Иначе
        1. Выберите случайную непосещенную клетку, сделайте ее текущей и отметьте как посещенную.

    Вы, вероятно, заметили что при выполнении условия 3, готовый лабиринт вероятнее всего будет иметь изолированную область.
    Это условие включено в алгоритм в порядке исключения, на практике при нормальной работе алгоритма и правильных исходных данных, оно не выполняется никогда.

    Реализация
    Как уже сказано выше, предполагается, что при начале работы алгоритма все клетки отделены стенками.
    Иллюстрация работы алгоритма
     0.    < - Начальная матрица.

    1.    < - Выбираем начальную точку стартовой.

    2.1.   < - Перемещаемся к случайному непосещенному соседу, пока таковые есть.

    2.2.   < - Непосещенных соседей нет. Возвращаемся назад по стеку, пока нет непосещенных соседей.

    2.1.   < - Непосещенные соседи есть. Перемещаемся к случайному непосещенному соседу.

    2.    < - Нет непосещенных клеток. Лабиринт сгенерирован.

    Программный код
    Приступаем к самому интересному.

    Начнем действовать по порядку и сначала сгенерируем начальную матрицу, с которой будет работать алгоритм.
    Для удобства условимся, что все типы клеток заданы в перечислении.

    Int maze; //создаем матрицу - двумерный массив for(i = 0; i < height; i++){ for(j = 0; j < width; j++){ if((i % 2 != 0 && j % 2 != 0) && //если ячейка нечетная по x и y, (i < height-1 && j < width-1)) //и при этом находится в пределах стен лабиринта maze[i][j] = CELL; //то это КЛЕТКА else maze[i][j] = WALL; //в остальных случаях это СТЕНА. } }
    Теперь, когда все приготовления сделаны, можно приступать к генерации.

    Typedef struct cell{ //структура, хранящая координаты клетки в матрице unsigned int x; unsigned int y; } cell; typedef struct cellString{ cell* cells; unsigned int size; } cellString;
    Структуры значительно упростят жизнь при обмене информацией между функциями.

    Отрывок кода, отвечающий за генерацию:

    Cell startCell = {1, 1} cell currentCell = startCell; cell neighbourCell; do{ cellString Neighbours = getNeighbours(width, height, maze, startPoint, 2); if(Neighbours.size != 0){ //если у клетки есть непосещенные соседи randNum = randomRange(0, Neighbours.size-1); neighbourCell = cellStringNeighbours.cells; //выбираем случайного соседа push(d.startPoint); //заносим текущую точку в стек maze = removeWall(currentCell, neighbourCell, maze); //убираем стену между текущей и сосендней точками currentCell = neighbourCell; //делаем соседнюю точку текущей и отмечаем ее посещенной maze = setMode(d.startPoint, d.maze, VISITED); free(cellStringNeighbours.cells); } else if(stackSize > 0){ //если нет соседей, возвращаемся на предыдущую точку startPoint = pop(); } else{ //если нет соседей и точек в стеке, но не все точки посещены, выбираем случайную из непосещенных cellString cellStringUnvisited = getUnvisitedCells(width, height, maze); randNum = randomRange(0, cellStringUnvisited.size-1); currentCell = cellStringUnvisited.cells; free(cellStringUnvisited.cells); } while(unvisitedCount() > 0);
    Как видно, реализация алгоритма проста и абстрактна от теории, как говорится, «справится даже ребенок».
    Чтобы не перегружать статью, код функций, используемых в вышеприведенном отрывке, под спойлером.

    Код функций

    Функция getNeighbours возвращает массив непосещенных соседей клетки

    CellString getNeighbours(unsigned int width, unsigned int height, int** maze, cell c){ unsigned int i; unsigned int x = c.x; unsigned int y = c.y; cell up = {x, y - distance}; cell rt = {x + distance, y}; cell dw = {x, y + distance}; cell lt = {x - distance, y}; cell d = {dw, rt, up, lt}; unsigned int size = 0; cellString cells; cells.cells = malloc(4 * sizeof(cell)); for(i = 0; i < 4; i++){ //для каждого направдения if(d[i].x > 0 && d[i].x < width && d[i].y > 0 && d[i].y < height){ //если не выходит за границы лабиринта unsigned int mazeCellCurrent = maze.y].x]; cell cellCurrent = d[i]; if(mazeCellCurrent != WALL && mazeCellCurrent != VISITED){ //и не посещена\является стеной cells.cells = cellCurrent; //записать в массив; size++; } } } cells.size = size; return cells;
    Функция removeWall убирает стенку между двумя клетками:

    MazeMatrix removeWall(cell first, cell second, int** maze){ short int xDiff = second.x - first.x; short int yDiff = second.y - first.y; short int addX, addY; cell target; addX = (xDiff != 0) ? (xDiff / abs(xDiff)) : 0; addY = (yDiff != 0) ? (yDiff / abs(yDiff)) : 0; target.x = first.x + addX; //координаты стенки target.y = first.y + addY; maze = VISITED; return maze; }
    Сначала вычисляется значение разности координат второй и первой точек. Очевидно, значение может быть либо отрицательное, либо положительное, либо 0.

    Надо найти такие координаты xy, чтобы при сложении их с координатами первой точки получались координаты стенки.

    Так как мы точно знаем, что вектор разности между координатами стенки и первой точке равен либо (|1|, 0) либо (0, |1|), мы можем этим воспользоваться.

    Таким образом, аддитив для x координаты при xDiff != 0 будет равен xDiff / |xDiff|, при xDiff = 0, нулю. Для y соответственно.
    Получив аддитивы для x и y, мы легко вычисляем координаты стенки между первой и второй клетками и назначаем клетку по этим координатам посещенной.


    Итак, теперь у нас есть генератор лабиринтов, который может строить запутанные лабиринты, размер которых ограничен только размером оперативной памяти.

    В итоге, мы можем получить что-то такое:

    Лабиринты. Осторожно, трафик!

    100x100


      500x500



    Генерация работает, теперь дело за малым: найти в таком лабиринте выход.

    Алгоритмов поиска пути несколько больше, чем алгоритмов генерации, и некоторые из них, если будет интерес читателей, я освещу в следующих статьях, но пока что будем довольствоваться тем, что есть, и «пройдем» лабиринт тем же алгоритмом.

    И все еще сильнее упрощается, так как нам больше не надо убирать стенки.

    Алгоритм поиска пути бэктрекингом:
    1. Сделайте начальную клетку текущей и отметьте ее как посещенную.
    2. Пока не найден выход
      1. Если текущая клетка имеет непосещенных «соседей»
        1. Протолкните текущую клетку в стек
        2. Выберите случайную клетку из соседних
        3. Сделайте выбранную клетку текущей и отметьте ее как посещенную.
      2. Иначе если стек не пуст
        1. Выдерните клетку из стека
        2. Сделайте ее текущей
      3. Иначе выхода нет

    Выходной точкой, как и стартовой, может выступать любая точка лабиринта, не являющаяся стенкой.
    Традиционно, выход должен быть «прижат» к одной из стенок, но по сути может находиться где угодно.
    Все таки, в данном случае, «вход» и «выход» - всего лишь две точки, между которыми надо найти путь.

    Критерий нахождения «выхода» очень прост: достаточно сравнить координаты текущей точки и координаты «выхода»: если они равны, путь между стартовой и выходной точками найден.

    Посмотрим что вышло:

    Вот и все, что нужно для самой простой реализации генератора случайных лабиринтов.

    Для тех, кто заинтересовался, полный исходный код проекта на GitHub.

    ВВЕДЕНИЕ

    компьютерный игра программный

    Ни для кого не секрет, что видео игры прочно заняли свою позицию в современной индустрии развлечений и развитий. Существуют попытки выделить компьютерные игры как отдельную область искусства, наряду с театром, кино и т.п. Разработка игр может оказаться не только увлекательным, развивающим, но и прибыльным делом, примеров этому предостаточно в истории. Первые примитивные компьютерные и видео игры были разработаны в 1950-х и 1960-х года.В настоящее время, разработка игры - это многомиллионный процесс, в котором задействована целая команда разработчиков, сложные современные технологии и даже маркетинговые ходы.Существует область человеческой деятельности, в которой лабиринты являются необходимым атрибутом - это компьютерные игры. Действительно, почти все игры приключенческих жанров и аркады используют лабиринты. Встречаются они и в других жанрах: логические игры, экшен. изредка в симуляторах. В связи с этим у пользователя появляются две задачи: прохождение лабиринтов в игровых программах и создание лабиринтов для "своих" игр. Целью выполнения курсовой работы является разработка компьютерной игры «Лабиринт», позволяющая развивать мышление, логику и непосредственно реакцию. Так же для разработки компьютерной игры, были поставлены следующие задачи:

    · пользователь должен передвигаться с помощью клавиш клавиатуры, с целью нахождения выхода из лабиринта за определенное время;

    · необходимо реализовать статистику прохождения игроков, в которойуказанно время прохождения, имя игрока и баллы за затраченное время. Баллы должны учитываться соответственно с затраченного времени на нахождение выхода из лабиринта;

    · разработать функцию редактора лабиринта.

    ТЕОРЕТИЧЕСКИЕ АСПЕКТЫ РАЗРАБОТКИ, НАЗНАЧЕНИЯ И ЭКСПЛУАТАЦИИ КОМПЬЮТЕРНОЙ ИГРЫ «ЛАБИРИНТ»

    Общие сведения о компьютерной игре «Лабиринт»

    Распространённым видом компьютерных игр являетсялабиринт.По определению это слово характеризуется с какой-либо структурой,(обычно в двухмерном или трёхмерном пространстве), состоящая из запутанных путей к выходу (и/или путей, ведущих в тупик). Под лабиринтом у древних греков и римлян подразумевалось более или менее обширное пространство, состоящее из многочисленных залов, камер, дворов и переходов, расположенных по сложному и запутанному плану, с целью запутать и не дать выхода несведущему в плане лабиринта человеку. В широком смысле слова лабиринт может представлять тупиковую ситуацию или дело, из которого очень сложно найти выход. Считается, что если проходить лабиринт, касаясь только одного из краев стенок лабиринта, то этот лабиринт обязательно будет пройден, хотя это не всегда верно: в лабиринте с несвязанными стенами этот способ может не сработать.

    Лабиринты известны человечеству с древних времен - много раз ветвящиеся узкие коридоры, в которых можно было без проблем заблудиться и остаться там очень надолго. Если вспомнить миф о Тесее и Минотавре, в котором главный герой Тесей расправился с Минотавром. С тех пор лабиринты стали нести оборонительную и развлекательную роль. Осаждающие крепость отряды терялись в коридорах из стен и башен и погибали под градом стрел и камней обороняющихся. Позже стали популярны и садовые лабиринты, в которых располагались скамейки, фонтанчики и другие элементы декора. Особенно часто их создавали в Англии и во Франции.Головоломки стали очень популярны приблизительно в конце 19 века. В них необходимо было найти выход из лабиринта, и разгадывались они, как правило, очень быстро. Со временем вышли в свет и объемные головоломки, в которых необходимо было завести или вывести шарик в центр, или из центраи многие другие разновидности реализации игры «Лабиринт».Главной особенностью игры является мышление ведь оно как ничто другое влияет на полноценное развитие и умственные способности человека. Со временем игра «Лабиринт» стала одной из самых популярных игр, развивающих логическое мышление.

    В этой статье речь пойдет о самом простом в реализации алгоритме генерации «идеального» лабиринта и его применении для поиска пути.

    Мы рассмотрим алгоритм, основанный на бэктрекинге, позволяющий создавать лабиринты без циклов, имеющие единственный путь между двумя точками. Алгоритм не самый быстрый, довольно требователен к ресурсам, по сравнению с алгоритмом Эйлера или Крускала, но очень прост в реализации и позволяет создавать ветвистые лабиринты с очень длинными тупиковыми ответвлениями.

    Заинтересовавшихся - прошу под кат.

    В русскоязычном интернете очень мало информации по алгоритмам генерации лабиринтов, что и стало причиной для написания этой статьи.
    Примеры кода на языке Си, а также полный исходный код проекта на GitHub доступны под лицензией GNU GPLv3.
    Ссылки на англоязычные ресурсы и проект вы найдете в конце статьи.

    Описание алгоритма
    Замечание: предполагается, что изначально у каждой клетки есть стенки со всех четырех сторон, которые отделяют ее от соседних клеток.


    2. Пока есть непосещенные клетки



        3. Уберите стенку между текущей клеткой и выбранной
        4. Сделайте выбранную клетку текущей и отметьте ее как посещенную.
      2. Иначе если стек не пуст

        2. Сделайте ее текущей
      3. Иначе
        1. Выберите случайную непосещенную клетку, сделайте ее текущей и отметьте как посещенную.

    Вы, вероятно, заметили что при выполнении условия 3, готовый лабиринт вероятнее всего будет иметь изолированную область.
    Это условие включено в алгоритм в порядке исключения, на практике при нормальной работе алгоритма и правильных исходных данных, оно не выполняется никогда.

    Реализация
    Как уже сказано выше, предполагается, что при начале работы алгоритма все клетки отделены стенками.
    Иллюстрация работы алгоритма
     0.    < - Начальная матрица.

    1.    < - Выбираем начальную точку стартовой.

    2.1.   < - Перемещаемся к случайному непосещенному соседу, пока таковые есть.

    2.2.   < - Непосещенных соседей нет. Возвращаемся назад по стеку, пока нет непосещенных соседей.

    2.1.   < - Непосещенные соседи есть. Перемещаемся к случайному непосещенному соседу.

    2.    < - Нет непосещенных клеток. Лабиринт сгенерирован.

    Программный код
    Приступаем к самому интересному.

    Начнем действовать по порядку и сначала сгенерируем начальную матрицу, с которой будет работать алгоритм.
    Для удобства условимся, что все типы клеток заданы в перечислении.

    Int maze; //создаем матрицу - двумерный массив for(i = 0; i < height; i++){ for(j = 0; j < width; j++){ if((i % 2 != 0 && j % 2 != 0) && //если ячейка нечетная по x и y, (i < height-1 && j < width-1)) //и при этом находится в пределах стен лабиринта maze[i][j] = CELL; //то это КЛЕТКА else maze[i][j] = WALL; //в остальных случаях это СТЕНА. } }
    Теперь, когда все приготовления сделаны, можно приступать к генерации.

    Typedef struct cell{ //структура, хранящая координаты клетки в матрице unsigned int x; unsigned int y; } cell; typedef struct cellString{ cell* cells; unsigned int size; } cellString;
    Структуры значительно упростят жизнь при обмене информацией между функциями.

    Отрывок кода, отвечающий за генерацию:

    Cell startCell = {1, 1} cell currentCell = startCell; cell neighbourCell; do{ cellString Neighbours = getNeighbours(width, height, maze, startPoint, 2); if(Neighbours.size != 0){ //если у клетки есть непосещенные соседи randNum = randomRange(0, Neighbours.size-1); neighbourCell = cellStringNeighbours.cells; //выбираем случайного соседа push(d.startPoint); //заносим текущую точку в стек maze = removeWall(currentCell, neighbourCell, maze); //убираем стену между текущей и сосендней точками currentCell = neighbourCell; //делаем соседнюю точку текущей и отмечаем ее посещенной maze = setMode(d.startPoint, d.maze, VISITED); free(cellStringNeighbours.cells); } else if(stackSize > 0){ //если нет соседей, возвращаемся на предыдущую точку startPoint = pop(); } else{ //если нет соседей и точек в стеке, но не все точки посещены, выбираем случайную из непосещенных cellString cellStringUnvisited = getUnvisitedCells(width, height, maze); randNum = randomRange(0, cellStringUnvisited.size-1); currentCell = cellStringUnvisited.cells; free(cellStringUnvisited.cells); } while(unvisitedCount() > 0);
    Как видно, реализация алгоритма проста и абстрактна от теории, как говорится, «справится даже ребенок».
    Чтобы не перегружать статью, код функций, используемых в вышеприведенном отрывке, под спойлером.

    Код функций

    Функция getNeighbours возвращает массив непосещенных соседей клетки

    CellString getNeighbours(unsigned int width, unsigned int height, int** maze, cell c){ unsigned int i; unsigned int x = c.x; unsigned int y = c.y; cell up = {x, y - distance}; cell rt = {x + distance, y}; cell dw = {x, y + distance}; cell lt = {x - distance, y}; cell d = {dw, rt, up, lt}; unsigned int size = 0; cellString cells; cells.cells = malloc(4 * sizeof(cell)); for(i = 0; i < 4; i++){ //для каждого направдения if(d[i].x > 0 && d[i].x < width && d[i].y > 0 && d[i].y < height){ //если не выходит за границы лабиринта unsigned int mazeCellCurrent = maze.y].x]; cell cellCurrent = d[i]; if(mazeCellCurrent != WALL && mazeCellCurrent != VISITED){ //и не посещена\является стеной cells.cells = cellCurrent; //записать в массив; size++; } } } cells.size = size; return cells;
    Функция removeWall убирает стенку между двумя клетками:

    MazeMatrix removeWall(cell first, cell second, int** maze){ short int xDiff = second.x - first.x; short int yDiff = second.y - first.y; short int addX, addY; cell target; addX = (xDiff != 0) ? (xDiff / abs(xDiff)) : 0; addY = (yDiff != 0) ? (yDiff / abs(yDiff)) : 0; target.x = first.x + addX; //координаты стенки target.y = first.y + addY; maze = VISITED; return maze; }
    Сначала вычисляется значение разности координат второй и первой точек. Очевидно, значение может быть либо отрицательное, либо положительное, либо 0.

    Надо найти такие координаты xy, чтобы при сложении их с координатами первой точки получались координаты стенки.

    Так как мы точно знаем, что вектор разности между координатами стенки и первой точке равен либо (|1|, 0) либо (0, |1|), мы можем этим воспользоваться.

    Таким образом, аддитив для x координаты при xDiff != 0 будет равен xDiff / |xDiff|, при xDiff = 0, нулю. Для y соответственно.
    Получив аддитивы для x и y, мы легко вычисляем координаты стенки между первой и второй клетками и назначаем клетку по этим координатам посещенной.


    Итак, теперь у нас есть генератор лабиринтов, который может строить запутанные лабиринты, размер которых ограничен только размером оперативной памяти.

    В итоге, мы можем получить что-то такое:

    Лабиринты. Осторожно, трафик!

    100x100


      500x500



    Генерация работает, теперь дело за малым: найти в таком лабиринте выход.

    Алгоритмов поиска пути несколько больше, чем алгоритмов генерации, и некоторые из них, если будет интерес читателей, я освещу в следующих статьях, но пока что будем довольствоваться тем, что есть, и «пройдем» лабиринт тем же алгоритмом.

    И все еще сильнее упрощается, так как нам больше не надо убирать стенки.

    Алгоритм поиска пути бэктрекингом:
    1. Сделайте начальную клетку текущей и отметьте ее как посещенную.
    2. Пока не найден выход
      1. Если текущая клетка имеет непосещенных «соседей»
        1. Протолкните текущую клетку в стек
        2. Выберите случайную клетку из соседних
        3. Сделайте выбранную клетку текущей и отметьте ее как посещенную.
      2. Иначе если стек не пуст
        1. Выдерните клетку из стека
        2. Сделайте ее текущей
      3. Иначе выхода нет

    Выходной точкой, как и стартовой, может выступать любая точка лабиринта, не являющаяся стенкой.
    Традиционно, выход должен быть «прижат» к одной из стенок, но по сути может находиться где угодно.
    Все таки, в данном случае, «вход» и «выход» - всего лишь две точки, между которыми надо найти путь.

    Критерий нахождения «выхода» очень прост: достаточно сравнить координаты текущей точки и координаты «выхода»: если они равны, путь между стартовой и выходной точками найден.

    Посмотрим что вышло:

    Вот и все, что нужно для самой простой реализации генератора случайных лабиринтов.

    Для тех, кто заинтересовался, полный исходный код проекта на GitHub.

    Лабиринты - это не только самостоятельный класс игр, но и основа для создания локаций в играх других жанров: например, систем пещер, которые, в свою очередь, могут быть использованы в очень широком классе игр-бродилок и т.д. Однако если игроку придется постоянно «изучать» одни и те же локации, ему это может скоро надоесть, а потому перед разработчиками игр встает вопрос о процедурной генерации лабиринтов, т.е. чтобы каждое очередное прохождение игры проходило на заново сгенерированной территории.

    Лабиринты мы будем строить на прямоугольном клеточном поле (+ одна из статей о генерации в трехмерном пространстве), поэтому все их можно разделить на две группы: лабиринты с «тонкими» стенками и с «толстыми». Первые - это те, у которых стены расположены на границах клеток, вторые - это те, в которых некоторые клетки сами являются непроходимыми, т.е. стенами. Их отличает, например, способ хранения данных о карте.

    Также существуют уже готовые решения для генерации лабиринтов: , который используется в DOOM, DOOM II и Heretic, и др.

    Алгоритм Эллера

    На тему генерации лабиринтов, где стенки расположены на границах клеток, на Хабре есть хороший перевод статьи «Eller’s Algorithm» (именно Эллера, а не Эйлера - «Eller’s», а не «Euler’s») о том, как создать идеальный (perfect) лабиринт - такой, что между любыми двумя его клетками существует путь, и притом единственный.

    Общая идея алгоритма заключается в построчной генерации, где между каждыми двумя клетками строки при определенных условиях (чтобы не было циклов и недоступных клеток) случайным образом возникает стенка. При этом в конце все клетки окажутся «в одном множестве», что будет означать, что между каждыми двумя клетками существует путь.

    Хранить карту лабиринта можно, например, в двух двумерных массивах: для вертикальных стенок и горизонтальных, соответственно.

    Как хранить лабиринты с «толстыми» стенками?

    Ответ на вопрос о хранении карт таких лабиринтов очевиден: в виде двумерного boolean массива, где, например, 1 - это непроходимая клетка (стена), 0 - свободная.

    Подробнее о картах на клеточных полях написано в статье «Tilebased games» . Теперь перейдем к самим лабиринтам генерации.

    Наивный алгоритм

    Лабиринт на таблице

    У описанного выше алгоритма есть один явный недостаток: проверять, пересекаются ли комнаты, приходится отдельной функцией. Возникает вопрос, можно ли не делать это лишнее действие. Оказывается, можно- и алгоритм описан ниже.

    Идея заключается в том, что поле изначально разбивается на прямоугольные «большие» клетки (т.е. не элементарные клетки игрового поля, а прямоугольники, состоящие из нескольких клеток), образуя таким образом таблицу. Далее в каждой такой ячейке случайным образом появляется комната случайного размера, не превосходящая размеров ячейки - тем самым возможность появления пересекающихся помещений пропадает. Затем комнаты объединяются коридорами, например, тем же способом, что описано в предыдущем пункте.

    Подробно этот алгоритм генерации описан в статье «Grid Based Dungeon Generator» .

    BSP деревья

    BSP - это аббревиатура от Binary Space Partitioning - двоичное разделение пространства. Этот алгоритм также позволяет избежать пересечения комнат еще в процессе помещения их на карту, т.к. также предварительно делит игровое поле на части - «листья», внутри которых затем генерирует комнаты. Это деление площади идейно сложнее, т.к. разделяет все, чем предыдущий алгоритм, но и позволяет создать более интересные конфигурации расположения помещений.

    Генерация лабиринтов с использованием клеточного автомата

    Каждый программист хотя бы раз писал «Жизнь» - клеточный автомат, придуманный математиком Конвэем. Так почему бы не использовать схожую идею для генерации лабиринтов? Суть предложенного алгоритма состоит в реализации всего двух шагов: сначала все поле заполняется случайным образом стенами - т.е. для каждой клетки случайным образом определяется, будет ли она свободной или непроходимой - а затем несколько раз происходит обновление состояния карты в соответствии с условиями, похожими на условия рождения/смерти в «Жизни».

    В источнике - на странице статьи «Generate Random Cave Levels Using Cellular Automata» - вы можете поэкспериментировать с интерактивной демкой, устанавливая различные значения для генерации: количество итераций обновления, граничные значения для жизни/смерти клетки и т.п. - и увидеть результат. Там же рассказывается о подводных камнях реализации.

    Даже при том, что Game Maker действительно прост в использовании и создание замысловатых игр осуществляется легко, но с первого взгляда его возможности могут привести тебя в замешательство и будет трудно понять, с чего же начать. Эта обучающая программа, как предполагается, продемонстрирует тебе, как сделать игру популярного типа: игру лабиринт. Шаг за шагом мы вместе с тобой проследим весь процесс создания игры. Самое заманчивое, что уже на первом этапе мы создадим простенькую игру, и уже после поэтапно мы будем улучшать созданный вариант и добавлять в него множество интересностей. Каждая из рассматриваемых частей игры предлагаются в виде отдельных файлов и в любой момент могут быть загружены в Game Maker. Основное и пожалуй единственное требование - для рассмотрения предлогаемого урока необходимо чтобы у тебя был установлен Game Maker версии 4, или более поздней.

    Игровая идея

    Перед тем как начать создание игры мы должны тщательно продумать, и четко представить себе идею будующей игры. Это очень важный (и в некотором смысле наиболее сложный) шаг в проектирование игры. Хорошая игра должна быть захватывающей, позволяющей удивить и увлечь. Желательно чтобы основная игровая задача была максимально понятна игроку, интерфейс пользователя должен быть интуитивен.

    Игра, которую мы собираемся создать - это игра лабиринтного типа. Каждая комната состоит из одного лабиринта. Чтобы выбраться из него, игрок должен собрать все алмазы и затем добраться до выхода. Необходимо решить все головоломки и избежать столкновений с населяющими лабиринт монстрами. Можно создать большое количество головоломок: блоки должны быть помещены в соответствующие выемки; участки комнат могут быть разрушены при помощи бомб и т.д. Очень важно не показывать все предлагаемые загадки сразу в первой комнате. Постепенное появление новых предметов и монстров, должно на долго задержать интерес игрока к твоей игре.

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

    Очень простое начало

    На первых порах мы не станем использовать алмазы. Мы создаем простую игру, в которой ты должен добраться до выхода. В игре присутствуют три основных компонента: игровой персонаж, стена и выход. Необходимо выбрать подходящие спрайты и создать объекты для каждого из них. Ты найдешь эту первую, очень простенькую игру, в файле под названием (maze game 1.gmd ). Загрузи его и познакомься что все это из себя представляет.

    Объекты

    Давай познакомимся с объектами. Размер каждого из них 32x32, все они имеют простые спрайты.
    • Стена (wall) : Объект стены - просто твердый (solid) объект. Другие объекты реагируют, когда они сталкиваются с ним. Твердыми являются только объекты стены. Мы можем создать их столько, сколько захочется.
    • Цель (goal) : Это объект выхода, до которого должен добраться игровой персонаж. Данный объект не является твердым. Я решил отобразить его в виде финишного флажка. Так будет понятнее игроку, что двигаться нужно именно к нему. Когда игровой персонаж сталкивается с ним, мы должны перейти в следующую комнату. Для этого нужно поместить действие (Go to the next room) в событие столкновения с персонажем (используем действие с рукой показывающей направо). В данном случае имеет место один недостаток. Данное действие вызывает ошибку, когда игрок заканчивает последний уровень. Самый простой способ решить эту проблему состоит в том, чтобы сделать последний уровень (комнату), непроходимым или лучше, вознаградить игрока каким-то экстравагантным способом (смотри позже в более сложных играх).
    • Персонаж (person) : Это основной игровой объект, которым управляет игрок. Здесь необходимо поместить некоторые действия. В особенности мы должны указать, как персонаж реагирует, на действия игрока.

    Перемещение персонажа

    Для перемещения мы будем использовать клавиши курсора. (Это естественно и так проще для играющего). Существует несколько способов, с помощью которых мы можем передвигать игрового персонажа. Самый простой состоит в том, чтобы переместить игрока на одну клетку в указанном направлении, когда игрок нажимает клавишу курсора. Второй способ, его мы и будем использовать, состоит в том, что персонаж перемещается в выбранном направлении, пока нажата клавиша. И еще один вариант заключается в том, чтобы сохранять (продолжать) перемещение персонажа, до тех пор, пока не нажата другая клавиша (подобно движениям в игре PacMan).

    Нам необходимо поместить действия для всех клавиш курсора. Действия элементарны. Они просто устанавливают соответствующее направление движения. (В качестве значения скорости мы используем 4). Единственная проблема - как остановиться, когда игрок отпускает клавишу. Это может быть достигнуто, использованием события клавиатуры для. Здесь мы выбираем отсутствие движения (красный квадратик по центру).

    Понятно, что мы также должны останавливать движение, когда персонаж касается стены. Для этого в событии столкновения для персонажа мы помещаем действие (start moving in a direction), которое останавливает движение (красный квадрат).

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

    С действиями мы, пожалуй, разобрались. Теперь давай создадим несколько комнат. Создай одну или две комнаты, чтобы они представляли из себя некий лабиринт. В каждой комнате помести объект выхода (goal) и объект персонажа (person) в стартовой позиции. Не забудь добавить комнату, в которой отсутствует выход и в заголовке напиши, например - "Не может быть пройден".

    Сделано

    Все! Первая игра готова. Попробуй немного поиграться. Например измени скорость персонажа в событии создания (creation), создай больше уровней, измени изображения и т.д.

    Собирание алмазов

    Но ведь цель нашей игры состояла в том, чтобы играющий собирал алмазы. Сами алмазы просты в создании. Но как сделать так, чтобы игрок не мог покинуть комнату, пока не собраны все алмазы? Для этого в мы добавляем объект двери (door). Объект двери будет вести себя подобно стене до тех пор имеется хоть один алмаз и исчезнет, когда все алмазы будут собраны. Ты можешь найти вторую игру в файле под названием (maze game 2.gmd ). Загрузи ее и посмотри как это работает.

    Объекты

    Помимо объектов стены, выхода и персонажа, нам понадобятся еще два объекта, с соответствующими спрайтами:
    • Алмаз (diamond) : Этот не твердый объект. Когда он сталкивается с персонажем, то просто уничтожается. Это все.
    • Дверь (door) : Она помещается в критическое место, чтобы заблокировать проход к выходу. Это твердый объект (чтобы избежать возможности прохождения через него персонажа). В событии шага (step) для него устанавливаем проверку (If the number of instances is a value) равно ли число алмазов 0 и если да (Equal), то объект уничтожает (Destroy the instance) себя. В акте столкновения (collision) персонажа с дверью мы должны остановливать движение.

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

    Теперь мы можем создать несколько комнат с алмазами. Обрати внимание, что первая комната лабиринта не содержит алмазов и мы может беспрепятственно покинуть ее. Это логично, потому что для начала игрок должен понять, что достижение флажка позволит ему покинуть комнату, а уже после этого можно предложить ему заняться непосредственно сбором полезных ископаемых. Давая название с подсказкой для второй комнаты с алмазами, ты даешь игроку понять, что следует делать.

    Улучшение движения

    Один нюанс, который ты вероятно заметил при запуске игры, состоял в том, что персонаж останавливается, в тот момент когда ты отпускаешь клавишу курсора и место его остановки - не обязательно соответствует расположению ячейки сетки. В связи с этим не всегда удается коректно заворачивать, там где имеются стены с углами. Допустим ты хочешь, чтобы персонаж всегда останавливался или изменял направление в соответствии с сеточными ячейками. Этого можно достигнуть, добавив действие вопроса (If instance is aligned with a grid), которое проверяет, выровнен ли образец с сеткой. Как значения сетки (snap) используется значение 32. Только, если ответ истинный (true), мы изменяем направление движения или останавливаем его.

    Продолжая разговор, хочется отметить существование момента, к которому следует отнестись более внимательно. Если спрайт твоего персонажа не полностью заполняет ячейку, что как правило и бывает, то может произойти то, что твой персонаж, при сталкновении со стеной, не будет выровнен с ячейкой сетки. (Если говорить точнее, это происходит, когда размер границы спрайта больше чем скорость изображения). В данном случае персонаж застрянет, потому что он не будет реагировать на клавиши (так как он не выровнен с сеткой), а таже он не сможет двигаться дальше (потому что там находится стена). Решение - сделать спрайт больше или в дополнительных параметрах спрайта, отключить точную проверку столкновения (precise collision checking), и в качестве поля ограничения указать изображение полностью.

    Сделаем это немного лучше

    Сейчас, когда наша базовая игра создана, давай общими усилиями сделаем ее немного лучше. Ты можешь найти улучшенную игру в файле под названием (maze game 3.gmd ). Загрузи ее и ознакомься с тем, о чем пойдет дальше речь.

    Стены

    Наши стены выглядят довольно уродливо. Поэтому давай вместо них создадим три стенных объекта: один для угла, один для вертикальных стен и один для горизонтальных. Присвой им соответствующие спрайты и сделай их твердым телом. Теперь немного подправив комнаты, ты несколько улучшишь их внешний вид. Для улучшения картинки можно также использовать фоновые изображения.

    Для того чтобы избежать необходимости определения событий столкновения персонажа с каждой их этих разных стен (а позднее так же само и для монсторов), мы используем замечательую возможность имеющуюся в Game Maker. Мы сделаем объект угловой стены (corner wall) материнским (parent) для других объектов стены (во вкладке advanced). Теперь мы должны определить столкновения только с угловой стеной. Это событие столкновения будет автоматически использоваться для других объектов стены. Для объекта двери мы также определим, в качестве родителя - угловую стену.

    Счет (Score)

    Теперь давай позволим игроку набирать игровые очки, для того чтобы он мог реально оценивать результативность прохождения игры. Делается это довольно просто. За каждый собранный (уничтоженный) алмаз мы начисляем игроку 5 пунктов (очков). Так в событии уничтожения (destroy event) алмаза мы добавляем 5 пунктов к игровому счету. Прохождение уровня дает 40 очков, для этого мы добавляем 40 пунктов к игровому счету в событии столкновения флажка (goal) с главным героем (person)

    Обрати внимание, что игровой счет автоматически отображается в заголовке окна. Это, согласись, немного не эстетично в смысле дизайна. Давай чтобы избежать этого создадим объект контроллера (controller) и присвоим ему спрайт с изображением компьютера. Этот объект будет помещен нами во все комнаты. Он будет производить этакий глобальный контроль за происходящим. В нужный момент мы будем использовать его, для отображения игрового счета. В событии рисования (drawing event) для него, мы устанавливаем цвет и размер шрифта, а затем используем действие рисующее тектовую информацию (draw text), для того чтобы нарисовать следующий текст:

    "Score: " + string(score)

    Это может показаться по началу странным. Так как текст начинается с кавычек, но в действительности перед нами формула. Первая часть это строка "Score: " к которой мы добавляем счет, преобразованный из числа в строку, используя функцию строки string(). Ты должен отключить пункт отображения счета в опциях.

    Стартовый экран

    Лучше всего начать игру с титульного экрана, который будет отображать название игры. Для этого мы используем первую комнату. Создаем фоновый ресурс с подходящей картинкой. (Во вкладке advanced ты можешь указать, чтобы не использовалась видеопамять). Этот фон мы используем для первой комнаты (лучше всего отключить, рисование цвета фона и сделать его не-плиточным). Объект стартового контроллера (start controller), он конечно должен быть невидимым, ждет в течение пяти секунд и затем перемещает игрока в следующую комнату. Вместе с этим, когда игрок нажимает клавишу (любую клавишу), он также перемещается в следующую комнату (Также стартовый контроллер устанавливает игровой счет в нулевое значение - 0).

    Создаваемая нами игра по умолчанию будет работать в полно-экранном режиме; при этом она смотрится наиболее симпатично. Данная установка указывается в форме (окне) игровых опций. Также следует указать, чтобы курсор не отображался на экране.

    Финальный (заключительный) экран

    Текущий заключительный экран оптимизма не вселяет. Для того чтобы улучшить его, мы создаем невидимый объект end_controller и помещаем его в специальную, финальную комнату.
    • end_controller : Он устанавливает сигнальные часы в значение 1. В событии сигнала он отображает сообщению, поздравляющее игрока с окончанием игры. После этого он показывает списку лучших резульатов и перезапускает игру. (Причина для помещения его в событие сигнала, а не в событие создания, в том что иначе экран не будет повторно перерисован).
    Адаптируем финальную комнату для этого.

    Звуки

    Игра без звуков - это не современно и несколько раздражает. В связи с этим наша игра нуждается в некоторых звуках. Прежде всего нем нужна ненавязчивая фонововая музыка. Для этого мы воспользуемся midi файлом. Запускаем эту музыкальную часть в стартовом контроллере (start_controller), проигрывая ее с использованием цикла (т.е. постоянно). Далее нам нужны подходящие звуковые эффекты для озвучивания подбираемых алмазов, открываемых дверей и момента, когда мы добираемся до финального флажка. Эти звуки вызываются в соответствующих событиях, описанных выше.

    Особо стоит заострить внимание на двух моментах. В связи с тем, что игрок способен подбирать алмазы достаточно быстро, может возникнуть ситуация когда потребуется одновременно проигрывать один и тот же звук - хорошо бы установить для этого звука некоторое количество буферов, например 4. Во-вторых, при достижении финального флажка, после проигрывания соответствующего звука, хорошо бы поместить действие небольшого ожидания (sleep), чтобы перед переходом в следующую комнату, имелась небольшая пауза.

    Добавление монстров

    Теперь наша игра прикольно начинается и смотрится доволно неплохо, но тем не менее, она все еще остается достаточно простой и играть в нее не интересно. В связи с чем нам просто необходим какой-то экшен, например в лице страшных и опасных монстров. Мы создадим двух монстров: один из которых двигается слева на право, а другой перемещается вверх и вниз. Добавление монстров в действительности осуществляется очень просто. Монстр представляет из себя объект, который начинает движение и изменяет направление своих перемещений всякий раз, когда косается стен. Когда игровой персонаж сталкивается с монстром, он погибает (уничтожается), и как следствие происходит рестарт уровня. Дадим нашему персонажу три жизни. Объект контроллера реагирует в тот мометн, когда число жизней становится равным 0. Ты найдешь обновленный вариант игры в файле (maze game 4.gmd ). Загрузи его и взгляни, чтобы понять о чем пойдет дальше речь.

    Объекты

    Нам понадобятся следующие новые объекты и соответствующие спрайты (и изменения для существующих объектов):
    • monster_lr : Это - объект монстра. В событии его создания он решает идти ему влево или вправо. Также, чтобы усложнить жизнь нашего героя, устанавливаем немного завышенную скорость. Когда происходит столкновение, он изменяет свое горизонтальное направление.
    • monster_ud : Точно такой же монстр, с единственной разницей - он перемещается вверх и вниз.
    • monster_all : Точно такой же монстр, только с более интересным перемещением. Всякий раз, когда он натыкается на стену, он сперва пробует развернуться на 90 градусов влево. Если ему это не удается, он пытается повернуться на 90 градусов вправо. Если и в этом случае он терпит неудачу, монстр возвращается тем же путем, которым он шел до этого.
    Чтобы избежать проблем, иногда происходящих с монстрами, мы снимаем точную проверку столкновения (precise collision checking) и устанавливаем поле ограничения для полного изображения.

    Когда первонаж сталкивается с монстром (monster_lr), мы должны проиграть какой-нибудь ужасный звук, вызвать небольшую задержку (sleep), после чего перезапустить комнату и уменьшить число жизней на одну. (Обрати внимание, что данная последовательность обязяательна). Объект контроллера, в событии "no more lives", отображает список лучших результатов и перезапускает игру. Делаем это для всех монстров. (Здесь мы не можем использовать родительский объект, так как иначе будет неясно что делать, когда монстр косается стен. Если мы используем событие того же самого монстра со углом стены или материнского монстра со стеной).

    Это все. Теперь мы можем начать создание уровней с монстрами. (Обрати внимание, что если во воемя игры, нажать клавишу то ты перенесешся в следующий уровень (комнату), а нажатие клавиши

    Вернет тебя в предыдущую комнату. Это может оказаться полезным при тестировании игры).

    Жизни

    Мы используем механизм жизней Game Maker, чтобы дать игроку три жизни. Объект контроллера делает это, точно так же, как и в случае со счетом. Простой показ числа жизней, выглядит вполне нормально. Но более прикольно, если вместо чисел, отождествляющих количество жизней, будут отображены миниатюрные изображения игрового персонажа. Для этого, в событии рисования контроллера мы помещаем следующую часть кода:
      {
      for (i=0; i<lives ;i+=1)
      draw_sprite_scaled(sprite_person,0,x +64+20*i,y +8,0.5);
      }
    Эта конструкция выглядит возможно несколько сложно, но тем не менее при помощи ее мы добиваемся именно того, что хотели. Для количества жизней выполняется цикл. В каждом выполнении масштабируемое изображение рисуется в соответствующем месте.

    Добавление в игру новых возможностей

    Чтобы внести в игру новые изменения, нам понадобится немного больше объектов. Ты можешь найти новую игру под названием maze game 5.gmd . Загрузи данный файл и познакомься с его содержимым.

    Бомбы

    Давай добавим бомбы и взрыватели, которые будут подрывать бомбы. Идея заключается в том что, когда игрок касается взрывателя, все бомбы взрываются, уничтожая все по соседству. Это может быть использовано, для создания проходов в стенах и для уничтожения монстров. Мы нуждаемся в трех новых объектах:
    • Взрыватель (trigger) : При соприкосновении с персонажем он должен активировать все бомбы, и взорвав их самоуничтожится.
    • Бомба (bomb) : Объект, который ничего не делает. Чтобы быть уверенным что монстры могут перемещаться поверх него этого (а не под ним) мы устанавливаем в качестве значения его глубины - 10.
    • Взрыв (explosion) : Взрыв всего лишь отображает анимацию. Он уничтожает все, чего касается. По окончанию данной анимации он уничтожает себя. (Тебе нужно быть внимательным, чтобы начало координат взрыва - соответствовало месту взрыва бомбы). Чтобы уничтожить все образцы всех объектов, которые окажутся рядом с ним, мы в его событии создания используем следующую часть кода:
      {
      with (all)
      {
      if (self != other && place_meeting(x ,y ,other )) instance_destroy();
      }
      }
      Обрати внимание, что это не будет правильно работать, если персонаж окажется рядом с бомбой! Поэтому удостоверься, что взрыватели не находятся рядом с бомбами.
    Очень важно тщательно продумывать уровни содержащие бомбы и взрыватели, так чтобы, данные уровни были действительно интересными.

    Камни и ямы

    Давай сделаем кое-что еще, что позволит нам создать более сложные головоломки. Мы создаем камни, которые игрок сможет перемещать. Создадим также несколько ям, через которые игрок не сможет перебраться, но в которые он сможет сбрасывать камни, создавая тем самым для себя новые проходы. Все это позволяет нам получить дополнительные возможности. Для того чтобы создать проход, необходимо правильным образом использовать камни. Также используя камни, ты можешь блокировать монстров.

    Нам понадобятся два следующих объекта:

      Камень (block) : Это твердый объект. Его основная сложность в том, что он должен следовать за движением персонажа, когда тот двигает камень. Когда камнь сталкивается с персонажем, мы предпринимаем следующие действия: проверяем, является ли относительная позиция 8*other.hspeed, 8*other.vspeed пустой. Это - позиция, куда должен быть перемещен камень. Если позиция пустая, то мы перемещаем камень на нее. Делаем то же самое, если в данной позиции находится объект ямы. Чтобы избежать возможности прохождения монстров через камень, делаем для него материнским объектом объект угловой стены. Сее допустимо, хотя при этом проявляется небольшая проблема. Поскольку событие столкновения определено между персонажем и угловой стеной, а не между персонажем и камнем, это событие выполняется, останавливая персонажа. Это не совсем то, что нам надо. Чтобы разрешить данную проблему, мы помещаем ложное действие (только комментарий) в событие столкновения персонажа с камнем. Теперь выполняется данное событие, не останавливая при этом персонаж.
      Яма (hole) : Является твердым объектом. Когда она сталкивается с камнем, то уничтожает себя и камень. Также мы сделаем его родительским объектом угловую стену, чтобы яма обладала теми же свойствами что и стена.
    Используя камни и ямы ты можешь создать много интригующих комнат (уровней). Хотя и здесь присутствует небольшая проблема. Ты можешь легко заблокировать себя камнем так, что в результате уровень не сможет быть пройден. В связи с этим мы должны дать игроку возможность перезапустить уровень, отняв у него при этом одну жизнь - воспользуемся клавишей (R), которая перезапускает уровень. Для этого в событии клавиатуры для контроллера мы просто перезапускаем комнату и вычитаем одну из жизней (именно в таком порядке, иначе если мы сначала исчерпаем все жизни и лишь затем перезапустим игру, то перезапустится уровень, который нам как бы уже будет и не нужен).

    Несколько скромных уточнений

    Улучшение графики

    Графика нашей текущей игры выглядит недостаточно хорошо. Поэтому давай кое-что подправим, чтобы улучшить ее. Ты найдешь уже измененый вариант, демонстриующий все ниже изложенное, в файле под названием (maze game 6.gmd ). Загрузите его, чтобы понять о чем пойдет речь.

    Основная вещь, которую мы хотим изменить, заключается в том, чтобы наш игровой персонаж поворачивался в сторону того направления, куда он реально движется (т.е. чтобы герой был более интерактивен). Самый простой способ достичь этого, это использовать новое изображение, которое состоит из 4 фрагментов, по одному для соответствующего направления движения. Обычно Game Maker циклически прокручивает все имеющиеся в спрайте фрагменты. Мы можем избежать этого непосредственно изменяя настройку переменной image_single. Эта переменная указывает, какой фрагмент изображения следует показать (0 - самый первый). Теперь в событии шага для персонажа мы добавляем следующую часть кода, который устанавливает индекс изображения согласно его текущему направлению:

      {
      if (hspeed < 0) image_single = 3
      else if (hspeed > 0) image_single = 2
      else if (vspeed < 0) image_single = 1
      else if (vspeed > 0) image_single = 0
      }
    Это - все. (Точнее не совсем все. В событии создания мы должны установить для image_single значение 0 чтобы избежать циклического прокручивания всех фрагментов в самом начале игры).

    Подобные действия мы можем проделать и для всех имеющихся монстров.

    Премии (Бонусы)

    Теперь давай добавим парочку бонусов: первый будет давать игроку 100 очков, а другой будет добавлять ему дополнительную жизнь. Они оба делаются чрезвычайно просто. Когда персонаж сталкивается с любым из них, проигрывается звук, после чего они самоуничтожаются, и добавляют несколько очков к игровому счету или 1 дополнительную жизнь к имеющемуся числу. Все.

    Односторонние проходы

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

    Теперь давай из всего этого сделаем реальную игру

    На данный момент мы создали достаточное количество объектов, но реальной игры у нас по прежднему так и не получилось. Очень важная часть в любой игре - создание уровней. Они должны изменятся от простого к сложному. В начале можно использовать только несколько объектов. Позднее будет появляться все больше и больше разнообразных объектов. Убедись, чтобы в игре сохранялся эффект неожиданности, этак до уровня 50-того или еще дальше. Желательно чтобы уровни по своей сложности были адаптированнны под каждого определенного игрока. Для детей, например подойдут одни головоломки, для взрослых же несколько иные и более сложные.

    Также любая серьезная игра должна иметь не менее серьезную документацию, например с описанием клавиш управления или что-то подобное. В Game Maker имеется возможность достаточно легко добавить документацию примо в саму игру.

    Наконец, игрок скорее всего не будет играть в игру, если весь игровой цикл приходится преодолеть за раз. В связи с чем тебе придется добавить механизм, для загрузки и сохранения игры. К счастью, это очень просто. Game Maker имеет встроенную функцию сохранения и загрузки. Клавиша (F5) сохраняет текущую игру, а (F6) загружает последнюю сохраненную игру. Если ты используешь данную возможность, то нужно указать об этом в документации к игре.

    Ты найдешь почти завершенную игру, включающую все вышеизложенное, в файле под названием (maze game final.gmd ). Загрузи ее, поиграйся и если появится желание то можешь даже изменить в ней все что твоей душе угодно. В частности ты можешь добавить в нее больше уровней (в данный момент их всего 20). Также ты можешь добавлять какие-нибудь дополнительные объекты, например ключи - которые открывают; транспортеры - которые перемещают тебе из одной точки лабиринта в другую; патроны - которыми твой персонаж будет стрелять, уничтожая монстров; двери - которые время от времени будут открываться и закрываются; лед на котором персонаж будет скольить в определенном направлении; стреляющие ловушки и т.д.

    В заключении

    Я надеюсь, что эта обучающая информация и сопутствующие игровые примеры помогут тебе в создании своих собственных игр на движке Game Maker. Не забывай сначала грамотно спланировать свою игру и уже только потом принимайся за ее создание делая это поэтапно, шаг за шагом (или лучше, объект за объектом). Для достижения определенной цели, всегда существует несколько разных способов. Так что, если что-то не будет получаться, пробуй сделать это иначе.

    Удачи!