RS Game Maker Community
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

 
Расширенный поиск
  Главная  Форум   Вики Блоги FAQ Игры Статьи Примеры Войти Регистрация  
Вики
Все желающие приглашаются редактировать вики. Можете писать о своих (или чужих) играх, размещать туториалы, постить статьи - главное навесить категорию.
Страниц: 1   Вниз
  Печать  
Создаем игру в жанре Match3 - часть 1,2
0 Пользователей и 1 Гость смотрят эту тему.
WertyXBOCT
Горжусь Россией!
Частый посетитель
***

Репутация: 32
Offline Offline

Сообщений: 311


^^O','(Uu)SJJ - собери котенка

23 Июня 2011 в 21:52
Всем приветики! Жанр игры 3 в ряд, пользуется большим успехом у девушек, так что если хотите удивить подружку, то цикл статей для вас! :)

Первым делом, стоит рассмотреть представителя жанра. Плохой пример, кучка примеров по лучше.

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

Еще стоит определиться с полем. Поля тоже бывают разные, бывают большего размера, квадратные, прямоугольные, сложной формы, основанные на гексагоне, или другой геометрической фигуре. Опять же, сложных путей не ищем, берем квадратное поле, размером 8х8. Меньше делать не стоит, т.к. придется часто сбрасывать поле из-за отсутствия комбинаций, а больше не стоит из-за большего количества срабатывающих комбинаций.
Третий шаг, выбираем количество шаров. Я выбрал 8. 4 обычных, 4 - бонуса. Опять же это число середнячек, можно больше, можно меньше. Любые из этих значений можно подобрать эмпирическим путем.


В итоге у нас получается квадратное поле 8х8, 8 гемов и простой обмен элементами.
Разберемся, какие объекты нам понадобятся. Конечно, это сами гемы, которые будут представлять себя на поле, перемещаться и падать. Второй объект это поле, которое будет контролировать объекты, следить чтобы не было лишних объектов, проверять гемы на комбинации, выводить подсказку и вообще быть главным.

Два объекта вполне достаточно для наброска игры. Сам код я писать не буду, потому что мне лень :).

Первым делом рассмотрим функции поля подробнее, и составим список:
Функции объекта поля:
    [*]Генерировать поле - Поле, должно уметь размещать гемы в случайном порядке по требованию, или по надобности (например, если нет возможных ходов).
    [*]Размещать элементы - элементы на поле должны перемещаться на свободные ячейки, заполнять "верхние" ячейки, перемещаться между собой, и все это должно происходить без "затыков".
    [*]Проверять комбинации - поле должно уметь находить комбинации элементов, удовлетворяющие правила удаления их с поля и собственно удалить их с поля. Это могут быть 3 и более элементов в ряд, элементы в виде крестика, четыре элемента в виде квадратика и т.д и т.п.
    [*]Искать подсказку - на мой взгляд самое интересное в этой игре, и в тоже время самое сложное и не менее важное. Я потратил день на написание простейшей подсказки, а вечером, когда гулял с девушкой, понял что моя подсказка работает не так как надо, и часть верных комбинаций не узнает. Все благодаря девушке, у которой богатый опыт игры в этот жанр. Так что настоятельно рекомендую поиграть в несколько таких игр, и попытаться выработать алгоритм поиска комбинаций. В самом крайнем случае, заведите девушку которая играет в такие игры. Может показаться, что без подсказки можно обойтись, но это не так. Как вы узнаете что комбинаций для поля нет?.. Верный ответ - когда вы не можете дать подсказку игроку. Если хотите оставить игрока наедине с полем, на котором невозможно сделать ход, то вашу игру ждет провал.
    [/list]
    Приступим к гемам:
    Функции объекта элемента:
      [*] Перемещение - гем должен уметь перемещаться по полю, следуя правилам перемещения. Например, гем должен вставать на ту клетку, с которой ушел его собрат, или меняться местами с другим гемом по требованию пользователя. При этом, гем должен извещать о своих действиях поле.
      [*] Удаление - гем должен правильно удаляться. Я рекомендую сделать буфер гемов, состоящий из W*H+max(W,H) элементов. Например, у нас поле 8 на 8, тогда нам нужны 8*8+8 = 72 элемента в буфере. При удалении гема, объект гема должен дезактивироваться и помещаться в буфер, для проживания :).
      [*] Восстановление(создание) - гем должен уметь восстанавливаться на поле после удаления, или при создании в определенном месте.
      [*] *Свободный полет - гем должен уметь отключаться от логики поля, и перемещаться свободно по экрану не воздействуя по правилам игры. Это необязательная функция.
      [/list]

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


      Часть вторая:
      В прошлой статье, мы остановились на том, что открыли GameMaker (желательно 8) и принялись ваять объекты.

      Первым нашим объектом будет objGem:
      Событие Draw
      draw_set_color(color); //Покажем цвет нашего гема
      var size;
      size = min(fieldWidth,fieldHeight) / 2; //Возьмем размер нашего элемента из данных о размере ячейки, проведем легкую оптимизацию
      draw_set_alpha(image_alpha) // Изменим альфа канал, это пригодится в будущем
      draw_circle(x + size, y + size, round(size * 0.9), 0); // Нарисуем наш гем, который пока что выглядит как простой круг
      draw_set_alpha(1); // Вернем альфа канал в "прежнее" состояние
      Т.к. код был написан заранее, то на лицо использование всяких фишек, которые еще не ясно зачем, и откуда взялись.
      color - переменная которая хранит цвет нашего гема. По ней мы будем отличать гемы друг от друга.
      fieldWidth, fieldHeight - ширина и высота в px нашего поля.
      На этом с гемом покончим до конца этой статьи.

      Приступим к написанию objField.
      Событие Create
      Как всегда, в этом событии происходит инициализация всех необходимых для работы нашего движка переменных.
      globalvar gemBuffer; // Объявляем переменную-буфер наших гемов

      gemBuffer = ds_stack_create(); // Гемы хранятся в стеке

      Width   = 8; // Задаем ширину и...
      Height  = 8; // Высоты поля, измеряется в гемах

      var i,j,size; // Не забываем удалять временные перменные!

      size = Width*Height + max(Width,Height); // Легкая оптимизация цикла, выбираем количество итераций. Формула взята из предыдущей статьи.

      for (i=0;i<size;i+=1)
             ds_stack_push(gemBuffer,gemNew()); // Заполняем буффер гемами. Рассмотрим все функции чуть позже.

      globalvar Field,
                   fieldX,fieldY,
                   fieldWidth,fieldHeight; // ОБъявляем глобальные перменные. Массив поля его положение в пространстве и размер.

      fieldWidth = 60; // Ширина одной ячейки в px (эту переменную мы уже видели)
      fieldHeight= fieldWidth; // Высота ячейки в px. Она взята такая же как ширина, но у вас может отличаться. Или в будущем может отличаться :).

      fieldX = room_width / 2 - (fieldWidth * Width) / 2;  // Размещаем наше поле в комнате
      fieldY = room_height / 2 - (fieldHeight * Height) / 2; // Вообще-то можно было использовать просто x и у, но мне уже лень исправлять.
                      
      for (i=0;i<Width;i+=1)
      for (j=0;j<Height;j+=1)
             gemReset(i,j); // Размещаем гемы на поле. (функции чуть попозже, потерпите)
                
      selected = noone; // Выделенный гем в данный момент (объявляем как никакой, или -4).
      animated = false; // В данный момент нет никакой анимации, и можно взаимодействовать с полем

      Смотрим в функции (помните?, я обещал!):
      Функция gemNew():objGem
      var gem; // Временная переменная
      gem = instance_create(0,0,objGem); // Создаем гем
      gem.color = 0; //Обнуляем цвет
      instance_deactivate_object(gem); // Дезактивируем гем, чтобы не мешал
      return gem; // возращаем новый гем
      Следующая функция сбрасывает значения гема, присваивает ему случайный цвет, и размещает его на поле.
      Функция gemReset(int,int,objGem=0):void
      var gem;
      if (argument3 == 0) gem = ds_stack_pop(gemBuffer); // Если нам нужен любой гем, то берем из буфера
      else                gem = argument3; // В противном случае, берем тот гем, который захотели

      instance_activate_object(gem); // Сразу же активируем его. (Если этого не сделать, то код ниже не работает)

      if (argument2 == 0) //Если мы не знаем какой цвет хотим применить (или хотим применить черный : )
             gem.color = choose( // Выбираем случайный цвет.
                 c_red,c_green,c_yellow,c_blue,
                 c_fuchsia,c_teal,c_ltgray,c_orange
             );
      else gem.color = argument2; // Иначе берем заказанный увет

      // Размещаем гем на поле
      gem.x = fieldX + fieldWidth * argument0;
      gem.y = fieldY + fieldHeight * argument1;
      // Запоминаем его положение
      gem.left    = argument0;
      gem.top     = argument1;
      // Сохраняем гем в массиве
      Field[argument0,argument1]  = gem;
      К этой функции, мы еще ни раз вернемся, и не один десяток раз ее изменим.
      Перейдем к следующему событию, step
      Событие Step
      В этом событии, мы будем проводить практические все операции, которые касаются игрового процесса. Пока же здесь только функции отвечающие за перемещение гемов на поле.
      if (animated = false) // Если в данный момент анимация НЕ выполняется
      if (mouse_check_button_pressed(mb_left)) // Если пользователь НАЖАЛ левую кнопку мыши
      {
             if (mouse_x>fieldX && mouse_y>fieldY)
             if (mouse_x<fieldX + fieldWidth * Width && mouse_y<fieldY + fieldHeight * Height) // Если курсор находился над полем
             {
                 var X,Y;
                 X = floor((mouse_x - fieldX) / fieldWidth); // Конвертируем координаты из px в элементы
                 Y = floor((mouse_y - fieldY) / fieldHeight);
                 selected = Field[X,Y]; // Запоминаем выбор
             }
             else  selected = noone; //Если клик произошел не на поле, снимаем выделение
      }
      if (selected != noone) // Если есть выделенный объект
      if (mouse_check_button_released(mb_left)) // И игрок ОТПУСТИЛ левую кнопку мыши
      {
             if (mouse_x>fieldX && mouse_y>fieldY)
             if (mouse_x<fieldX + fieldWidth * Width && mouse_y<fieldY + fieldHeight * Height)
             {
                 var X,Y;
                 X = floor((mouse_x - fieldX) / fieldWidth);
                 Y = floor((mouse_y - fieldY) / fieldHeight);
                 if (gemDistance(Field[X,Y],selected)==1) //Проверяем на каком расстоянии (в элементах) находится гемы, если расстояние равно 1, то все нормально
                     gemMove(Field[X,Y],selected); // Обмениваемся местами
             }
             selected = noone; // Сбрасываем выделение, где не произошел клик
      }
      Функция gemDistance(objGem,objGem):int
      Эта функция возвращает расстояние между гемами. Расстояние измеряется в элементах, и имеет довольно специфичный способ расчета.
      return max(abs(argument0.left-argument1.left),abs(argument0.top-argument1.top),abs(argument0.left-argument1.left)+abs(argument0.top-argument1.top))   ;
      Выбирается самое большое значение из расстояние по горизонтали, по вертикали и суммы этих расстояний. Сделано это чтобы можно было перемещать гемы только по вертикали и по горизонтали на одну клетку. Т.е. если гем находится по диагонали вверх-вправо, то функция вернет значение 2, хотя, если бы не было суммы расстояний значение было бы 1, и наша программа переместила гемы. Но мы же не дураки :).
      Функция gemMove(objGem,objGem):void
      var gemOne,gemTwo; //Для удобства, присваиваем названия нашим гемам, полученным через аргументы
      gemOne = argument0    
      gemTwo = argument1;
      map = ds_map_create(); // Этот код не обязателен, но с ним эффектней :).
               ds_map_add(map,"x",gemTwo.x); //Можно сказать, присваиваем первому гему значения x и y от второго
               ds_map_add(map,"y",gemTwo.y);
      animate(gemOne,750,map,"easeOutElastic"); // Эту функцию подробно рассматривать не буду
      map = ds_map_create();
               ds_map_add(map,"x",gemOne.x); //Аналогично для второго гема.
               ds_map_add(map,"y",gemOne.y);
      animate(gemTwo,750,map,"easeOutElastic","objField.animated = false"); // Вызываем "магическую" : ) функцию для второго объекта. Обратите внимание на последний аргумент
      var X,Y;
      // Обмениваемся значениями left и top между гемами
      X = gemOne.left;
      Y = gemOne.top;
      gemOne.left = gemTwo.left;
      gemOne.top = gemTwo.top;
      gemTwo.left = X;
      gemTwo.top = Y;
      // Говорим полю, о новом месте проживания гемов
      Field[gemOne.left,gemOne.top] = gemOne;
      Field[gemTwo.left,gemTwo.top] = gemTwo;

      animated = true // Запрещаем полю взаимодействовать с игроком.
      В принципе, на этом наш код заканчивается. Основная логика описана. Осталась только "магическая" : ) функция.

      Магическая функция animate(object,number,map,string,string):void
      Эта функция взята из кода моего друга Йакуда, который написал ее для своего GMON'а. К сожалению, я не смог завести ее в первоначальном виде (и был жутко зол на вышеупомянутого товарища : ))
      Мне пришлось ее сильно переписать. Теперь она принимает другие параметры в качестве аргументов и работает по другому. От кода Йакуда почти нетронутым осталось только animate_easing (ну и функция empty).
      Какие аргументы принимает функция:
      Любой объект GM - object - это то, что мы будем анимировать
      Время в миллисекундах - number - как долго будем анимировать
      Мэп - map - где ключ является именем перменной в анимируемом объекте, а значение - его будущим значением.
      Easing - string - название функции анимирования, которая будет использовать для вычисления промежуточных вычислений. Названия этих функций состоят из трех частей:
      ease[In,Out,InOut][название функции]
      In,Out,InOut - показывает когда эта функция будет применяться, в начале анимации, в конце или на обоих сторонах.
      Название функции бывают разные, я применил Elastic, есть еще Quad, Cubic, Quart, Quint, Sine, Expo, Circ, Back и Bounce. У каждой из этих функций есть своя кривая значений, некоторые из этих функций представляют собой просто график какой либо математической функции, например Quad это x^2, а Cubic это x^3, другие специальный эффекты, например Elastic - резинка, Back - с возвратом, Bounce - отскок.
      Скрипт, выполняемый после анимации - string - например, мы хотим после анимации вернуть значение animated к false. Что мы и делаем :).

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

      Исходник, о котором идет речь во второй части (ссылка прямая на gmk файл, возможны проблемы при скачивании в опере)
      Последнее редактирование: 06 Июля 2013 в 17:35 от Огион

      www.weslompo.ru - мой тихий бложек :).
       
      Огион
      Завсегдатай
      ****

      Репутация: 131
      Offline Offline

      Сообщений: 959


      Ответ № 1 23 Июня 2011 в 22:29
      Молодца, WertyXBOCT!
      Перенес в раздел "Работа с Game Maker".
      возможны проблемы при скачивании в опере
      opera:config -> User Prefs -> Trust Server Types -> true

      И код лучше все-таки в [gml][/gml] вставлять.
      WertyXBOCT
      Горжусь Россией!
      Частый посетитель
      ***

      Репутация: 32
      Offline Offline

      Сообщений: 311


      ^^O','(Uu)SJJ - собери котенка

      Ответ № 2 23 Июня 2011 в 22:30
      Спасибо. Я импортировал с другого форума, проверил, вроде бы подсвечивается в тегах code. Сейчас поправлю.

      www.weslompo.ru - мой тихий бложек :).
      Kchiuk
      Пафосный
      Ветеран форума
      *****

      Репутация: 84
      Offline Offline

      Награды:
      Легенда сайта
      Сообщений: 1 961


      ):

      Ответ № 3 23 Июня 2011 в 22:49
      O_O внезапно!
      заставлю себя осилить всё, но это однозначно уважение, уже полгода мечтал о такой статье. O0

      Я клёвый.

      Макасин
      [s]
      Младший администратор
      Старожил
      ******

      Репутация: 341
      Offline Offline

      Награды:
      Легенда сайта
      Сообщений: 4 254


      [/s]

      Ответ № 4 23 Июня 2011 в 23:48
      Я так понял, будет продолжение, про проверку комбинаций, самого "три-в-ряд"?
      Однозначно ставлю плюс, статья хорошая, понятная. Тему закрепил.

      Энтузиазм нашему форуму чужд. Sad but true.
      Ogion.
      WertyXBOCT
      Горжусь Россией!
      Частый посетитель
      ***

      Репутация: 32
      Offline Offline

      Сообщений: 311


      ^^O','(Uu)SJJ - собери котенка

      Ответ № 5 24 Июня 2011 в 00:10
      Да продолжение будет, возможно еще будет одна, а может даже две статьи. Смотря во сколько у меня уместится алгоритм завершения хода и подсказки.

      www.weslompo.ru - мой тихий бложек :).
      Райдо
      Старожил
      ******

      Репутация: 89
      Offline Offline

      Награды:
      Легенда сайта
      Сообщений: 2 636


      Ответ № 6 24 Июня 2011 в 18:50
      Теории маловато, одно только голое "скопипастьте вот это вон туда". Прямо как на лекции какого-нибудь профессора, который болт клал на ваше понимание предмета - отчитал полтора часа и получил зарплату.

      WertyXBOCT
      Горжусь Россией!
      Частый посетитель
      ***

      Репутация: 32
      Offline Offline

      Сообщений: 311


      ^^O','(Uu)SJJ - собери котенка

      Ответ № 7 24 Июня 2011 в 23:01
      Ну а что я могу сказать?
      Пока что суть сводится к тому, что нужно сделать некоторые подготовительные работы. Представления данных в программе сделать так, чтобы потом можно было не отвлекаться на всякие мелочи. Основные алгоритмы - это удаление элементов и поиск подсказок, они рассматриваются в последующих уроках, и без базы они бессмысленны.
      Да, они будут в основном представлены также кодом, я мог бы нарисовать 100500 схем, алгоритмов, описать все, но это лишнее. Нужно уметь читать код, а уж кто-как себе представляет алгоритмы не моя забота.

      В целом, спасибо за комментарий, хотелось бы больше конструктивной критики и советов "как сделать лучше".

      www.weslompo.ru - мой тихий бложек :).
      Макасин
      [s]
      Младший администратор
      Старожил
      ******

      Репутация: 341
      Offline Offline

      Награды:
      Легенда сайта
      Сообщений: 4 254


      [/s]

      Ответ № 8 24 Июня 2011 в 23:10
      ...но это лишнее. Нужно уметь читать код...

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

      Энтузиазм нашему форуму чужд. Sad but true.
      Ogion.
      Райдо
      Старожил
      ******

      Репутация: 89
      Offline Offline

      Награды:
      Легенда сайта
      Сообщений: 2 636


      Ответ № 9 24 Июня 2011 в 23:14
      хотелось бы больше конструктивной критики и советов "как сделать лучше"
      Ну вот сейчас ты, например, прочитал мой комментарий, но полностью проигнорировал его суть, о чём довольно ясно заявляешь; дальнейшее сотрудничество невозможно. И таки схемы и алгоритмы рисовать надо, иначе будет тупой копипастинг.
      два профи обмениваются опытом, своей техникой
      Вот именно: игроделы высокого уровня не нуждаются в такого рода статьях, они могут всё сделать сами, а для новичков это не подходит по указанным выше причинам.

      WertyXBOCT
      Горжусь Россией!
      Частый посетитель
      ***

      Репутация: 32
      Offline Offline

      Сообщений: 311


      ^^O','(Uu)SJJ - собери котенка

      Ответ № 10 24 Июня 2011 в 23:27
      Я не понимаю, ты меня троллишь что-ли? Чего такого ты написал в том своем комментарии, что я упустил?
      Давай дельные предложения по повышению качества статей. Я уже четыре года как "не новичек" и забыл что мне требовалось для обучения.
      Какие схемы требуется? О каких алгоритмах идет речь? Поясни, что тебе (или, возможному новичку) не понятно.
      А тупой копипастинг будет во первых всегда, во вторых не так уж он и плох. В этот код все равно придется лезть, чтобы добавить свою фичу, когда нибудь человек разберется, если захочет разбираться.

      www.weslompo.ru - мой тихий бложек :).
      Райдо
      Старожил
      ******

      Репутация: 89
      Offline Offline

      Награды:
      Легенда сайта
      Сообщений: 2 636


      Ответ № 11 24 Июня 2011 в 23:28
      не так уж он и плох
      Сей тонущий корабль я покидаю, господа.

      WertyXBOCT
      Горжусь Россией!
      Частый посетитель
      ***

      Репутация: 32
      Offline Offline

      Сообщений: 311


      ^^O','(Uu)SJJ - собери котенка

      Ответ № 12 24 Июня 2011 в 23:55
      Спасибо. Без троллей, правда легче жить.

      Статейка появится в среду-четверг. У меня экзамены.
      Последнее редактирование: 28 Июня 2011 в 02:19 от WertyXBOCT

      www.weslompo.ru - мой тихий бложек :).
      Страниц: 1   Вверх
        Печать  
       
      Перейти в:  

      RSGMC (gmakers.ru) © 2007—2017
      Счётчик–@Mail.ru