TowerDefence #6. Редактор уровней

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

Ранее я уже пытался начать серию записей о редакторе уровней, но все так и закончилось на одной записи. С тех самых пор я больше не занимался разработкой новых редакторов уровней и ничего нового не придумывал. Поэтому принцип работы редактора уровня будет примерно таким же, как и в прошлой записи, но с тем лишь исключением, что сегодня мы более подробно рассмотрим другую его сторону — сохранение и загрузку игровых уровней, а так же отделим редактор уровней от самой игры. От слов к делу!

Editor.as

Первым делом давайте создадим класс Editor.as в папке с нашим игровым проектом — этот класс будет аналогом класса Game.as. Основное отличие Editor.as от Game.as будет заключаться в реализации интерфейса для создания, сохранения и загрузки уровней. Но при этом в самом редакторе уровней мы так же будем использовать класс мира Universe.as.

Поскольку на текущем этапе Editor.as ничем не будет отличатся от класса Game.as, то вы можете полностью скопировать функционал Game.as в Editor.as. Но не забудьте переименовать в исходном коде название класса и метод конструктор с «Game» на «Editor».

Поскольку теперь мы используем класс игрового мира в режиме игры и в режиме редактора уровней, нам следует сообщать игровому миру, в каком режиме он сейчас находится, чтобы далее в коде самого мира адекватно обрабатывать действия в зависимости от состояния. Поэтому теперь откроем Universe.as и добавим приватную переменную _isEditor, и сеттер для нее:

private var _isEditor:Boolean = false;

public function set editorMode(value:Boolean):void
{
  _isEditor = value;
}

После этого в классе Editor.as сразу сделаем переключение игрового мира в режим редактирования после его создания:

_universe.editorMode = true;

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

Отделение редактора уровней

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

Откройте *.fla и создайте в библиотеке клип с типом Button и назовите его «Game_btn». Нарисуйте в кадрах Up, Over, Down состояния вашей кнопки. Потом скопируйте получившуюся кнопку и переименуйте её в «Editor_btn», а так же не забудьте изменить её текстовую метку в кадрах Up, Over и Down, чтобы у вас не получилось две одинаковые кнопки. Когда обе кнопки готовы, обязательно в свойствах кнопки поставьте экспорт в ActionScript. Так же для аккуратности и наглядности все кнопки, используемые для редактора уровней, следует положить в новую папку «_EDITOR» в библиотеке клипов *.fla файла.

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

Когда кнопки готовы, откройте класс App.as для редактирования и добавьте новые приватные переменные:

private var _editor:Editor;
private var _btnGame:SimpleButton;
private var _btnEditor:SimpleButton;

Теперь создадим метод init():

public function init(event:Event = null):void
{
  // Кнопка "Game"
  _btnGame = new Game_btn();
  _btnGame.x = SCREEN_WIDTH_HALF;
  _btnGame.y = SCREEN_WIDTH_HALF - 120;
  _btnGame.addEventListener(MouseEvent.CLICK, gameClickHandler);
  addChild(_btnGame);
			
  // Кнопка "Editor"
  _btnEditor = new Editor_btn();
  _btnEditor.x = SCREEN_WIDTH_HALF;
  _btnEditor.y = SCREEN_HEIGHT_HALF + 40;
  _btnEditor.addEventListener(MouseEvent.CLICK, editorClickHandler);
  addChild(_btnEditor);
}

В данном коде все достаточно просто: мы создаем кнопки из наших клипов в библиотеке *.fla, устанавливаем их по центру экрана со смещением, добавляем слушателей события клика и добавляем их на сцену. Теперь изменим конструктор класса App.as и сделаем его похожим на конструкторы классов Game.as и Editor.as:

public function App()
{
  trace(APP_VERSION);
			
  if (stage)
  {
    init();
  }
  else
  {
    addEventListener(Event.ADDED_TO_STAGE, init);
  }
}

Осталось написать обработчики кликов по кнопкам, они будут выглядеть так:

private function gameClickHandler(event:Event):void
{
  _game = new Game();
  addChild(_game);
  free();
}
		
private function editorClickHandler(event:Event):void
{
  _editor = new Editor();
  addChild(_editor);
  free();
}

В зависимости от того, на какую кнопку мы кликнули, мы создаем соответствующий класс игры или редактора. Далее код загадочного метода free():

private function free():void
{
  removeChild(_btnGame);
  removeChild(_btnEditor);
			
  _btnGame.removeEventListener(MouseEvent.CLICK, gameClickHandler);
  _btnEditor.removeEventListener(MouseEvent.CLICK, editorClickHandler);
			
  _btnGame = null;
  _btnEditor = null;
}

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

import flash.events.Event;
import flash.display.SimpleButton;
import flash.events.MouseEvent;

Теперь можно протестировать приложение. Но, пока, на какую кнопку не кликни, результат мы будем видеть одинаковый. Все это потому, что наш редактор на данный момент обладает точно таким же функционалом, как и сама игра.

После того, как мы добавили возможность выбора игры или редактора в классе App.as, я надеюсь, что вам стал понятен принцип работы структуры игры, и необходимость в использовании такой цепочки классов. Позже когда мы приступим к работе над графикой, класс App.as станет основным классом, в котором будет проходить вся необходимая инициализация и подготовка игровых ресурсов прежде, чем начнется игра.

Способ хранения уровней

Прежде, чем приступать к разработке редактора уровней, мне хотелось бы немного рассказать о том, как мы будем хранить эти самые уровни. Игра в жанре TowerDefence достаточно не простая с точки зрения хранения информации об уровнях, поэтому я думаю, что здесь будет использовано сразу несколько способов.

Я считаю, что самый простой и удобный способ хранения уровней — это непосредственно во Flash IDE или в игровых классах. В данной игре сами игровые уровни хранить во Flash IDE не получится, так как в них будет много информации о вражеских волнах, об интервалах атак и много чего еще. Поэтому я буду использовать способ хранения уровней непосредственно в игровых классах.

Но при хранении игровых уровней в классах есть существенные недостатки — это невозможность сохранить уровень из редактора в игру, а так же, для того, чтобы внести изменения в игровой уровень, прийдется перекомпилировать всю игру. Впрочем, все эти недостатки можно обойти: например, чтобы поиграть в редактируемый уровень, достаточно его сохранить в памяти и переключиться в игровой класс и там загрузить его из памяти. А если в игровом уровне много игровых параметров, которые необходимо регулировать во время игры, то можно вынести все эти важные параметры во внешний xml файл (возможно для каждого уровня свой). Когда игра будет готова, все xml файлы можно будет легко вшить в оригинальном виде прямо в классы уровней, благодоря нативной поддержки XML в AS3.

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

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

  1. Flash IDE — не знаю как вам, но рисование тайлов с последующей их подгонкой друг к другу, достаточно затратное занятие по времени. Поэтому мне кажется наиболее оптимальным решением использовать в качестве игровых бэкграундов заготовленные заранее задники в виде составных MovieClip'ов во Flash IDE, например так же, как я делал это для грузовиков, либо использовать одну большую нарисованную картинку в любой другой программе.
  2. XML — в XML формате мы будем хранить данные о врагах и вражеских волнах: когда и сколько врагов будет выбегать, сколько вражеских волн будет и т.п.
  3. Класс — в классе уровня мы будем хранить карту проходимости, ссылку на клип с графическим представлением уровня и ссылку на внешний xml файл, а в финальной сборке содержимое xml документа. То есть, класс игрового уровня будет представлять собой некий скрипт, который будет связывать все данные об уровне вместе и выполнять их корректную загрузку в игровой движок.

 

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

Сохранение уровней

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

Чтобы сохранить, а вернее экспортировать уровень из редактора, нужно сделать кнопочку, которая будет выводить данные в окно Output. Сделайте кнопку так же, как мы это уже сделали для окна выбора игры/редактора, можете так же скопировать одну из готовых кнопок и изменить её. Назовем новую кнопку «Save_btn» и экспортируем в AS с таким же именем. Теперь откроем Editor.as для редактирования и добавим новую приватную переменную:

private var _btnSave:SimpleButton;

В методе init() класса добавим следующий код:

// Создаем кнопку сохранить
_btnSave = new Save_btn();
_btnSave.x = _btnSave.width / 2 + 10;
_btnSave.y = App.SCREEN_HEIGHT - _btnSave.height / 2 - 10;
_btnSave.addEventListener(MouseEvent.CLICK, saveClickHandler);
addChild(_btnSave);

В данном коде мы просто создаем кнопку из экземпляра нарисованной кнопки, выравниваем её по левому нижнему углу и добавляем обработчик. Теперь напишем обработчик клика saveClickHandler():

private function saveClickHandler(event:MouseEvent):void
{
  exportToOutput();
}

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

private function exportToOutput():void
{
  var mapMask:Array = _universe.mapMask;
  var mapWidth:int = mapMask[0].length;
  var mapHeight:int = mapMask.length;
  var line:String = "";
			
  for (var ay:int = 0; ay < mapHeight; ay++)
  {
    for (var ax:int = 0; ax < mapWidth; ax++)
    {
      // Для последней ячейки в строчке не ставим запятую
      if (ax == mapWidth - 1)
      {
        line += mapMask[ay][ax].toString();
      }
      else
      {
        line += mapMask[ay][ax].toString() +", ";
      }
    }
				
    // Выводим маску проходимости в формате кода 
    // инициализации нового двумерного массива
				
    // Первая строка
    if (ay == 0)
    {
      trace("_mapMask = [ [", line + "], ");
    }
    // Последняя строка
    else if (ay == mapHeight - 1)
    {
      trace("[", line + "] ];");
    }
    // Промежуточные строки
    else
    {
      trace("[", line + "],");
    }
    
    line = "";
  }
}

Экспорт карты проходимости в окно Output достаточно простое действие — мы перебираем всю карту проходимости и записываем значения в строку. Когда каждая строчка карты проходимости пройдена, мы выводим её в окно Output и очищаем для новых значений. Большая часть кода метода exportToOutput() — это условия, предназначенные для того, чтобы результат выглядел в виде инициализации нового двухмерного массива, который мы бы могли скопировать и вставить в наш класс уровня. Сейчас вы можете скомпилировать код и посмотреть на результат. Правда, при компиляции приложения Flash наругается на неизвестный тип SimpleButton — как вы думаете, что мы забыли сделать?

Менеджер уровней

Прежде, чем создавать загрузку уровней, нам нужно написать пару дополнительных классов, которые в будущем упростят нам работу с уровнями. Этими классами у нас будут LevelManager.as и LevelBase.as.

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

Давайте создадим новую папку/пакет в нашем проекте с именем levels, которая будет находится по адресу com.towerdefence.levels и создадим в ней новый файл LevelManager.as. Сразу в новом классе добавим статическую публичную константу TOTAL_LEVELS, в ней у нас будет храниться общее количество уровней в игре, и пока это значение будет ровняться одному уровню.

public static const TOTAL_LEVELS:int = 1;

После добавим приватную переменную _completed — в ней у нас будет храниться количество пройденных / доступных для игры уровней. Значение этой переменной пока сделаем так же равной 1, а потом это значение будем загружать из пользовательского сохранения (cookies).

private var _completed:int = 1;

Конструктор класса LevelManager.as пока оставим пустым и напишем публичный метод getLevel():

public function getLevel(levelId:int):LevelBase
{
  if (levelId < 0 || levelId > TOTAL_LEVELS)
  {
    trace("LevelManager::getLevel() - Уровня", levelId, "не существует!");
    return null;
  }
			
  switch (levelId)
  {
    case 1 :
	  return new Level1();
    break;
				
    default :
      return null;
    break;
  }
}

Цель этого метода — создать и вернуть экземпляр уровня с указанным номером. Как можно видеть из кода его реализация очень проста. Первым делом мы выполняем проверку на существование уровня, заметьте эта проверка нужна в первую очередь для разработчика чтобы потом, когда мы вдруг укажем неверный номер уровня, мы сразу увидим почему уровень не загрузился, и таким образом мы сможем сэкономить себе в будущем от 5 до 30 минут времени на поиски возможной ошибки :) Далее после проверки в зависимости от номера мы просто создаем и возвращаем соответствующий номеру экземпляр уровня. В целом, это пока весь функционал LevelManager.as, который нам требуется на данный момент.

Теперь создаем класс LevelBase.as, он должен располагаться так же в папке com.towerdefence.levels. В теле класса создадим две наследуемых переменные:

protected var _universe:Universe = Universe.getInstance();
protected var _mapMask:Array;

Здесь у нас ссылка на игровой мир и массив, в котором каждый уровень будет хранить карту проходимости. Далее добавим новый метод load(), который будет загружать каждый уровень в игровой мир:

public function load():void
{
  _universe.mapMask = _mapMask;
}

На текущем этапе разработки загрузка уровня выглядит предельно просто, мы передаем указатель на маску проходимости из игрового уровня. Но будьте внимательны, сам класс LevelBase.as не будет содержать в себе карты проходимости. Так же не забудьте добавить в класс LevelBase.as импорт класса Universe.as:

import com.towerdefence.Universe;

На этом мы пока заканчиваем с LevelBase.as и приступаем непосредственно к реализации класса первого игрового уровня. Создаем класс Level1.as — он так же должен располагаться в папке com.towerdefence.levels, и самое важное, он должен быть унаследован от класса LevelBase.as (!). Конструктор класса Level1.as оставим пока пустым и реализуем для начала работу LevelManager.as в игре.

Открываем класс Editor.as и добавляем пару приватных переменных:

private var _levelManager:LevelManager;
private var _currentLevel:LevelBase;

В переменной _levelManager у нас будет находиться менеджер уровней, а в _currentLevel текущий игровой уровень. Вообще я думаю, что переменную _currentLevel следует перенести в публичные переменные класса Universe.as, но пока пусть будет так. Теперь добавим загрузку уровня в метод init() класса Editor.as следующими строками:

// Создаем менеджер уровней
_levelManager = new LevelManager();
			
// Загружаем первый уровень
_currentLevel = _levelManager.getLevel(1);
_currentLevel.load();

Таким образом, загрузка уровня у нас получается всего двумя строчками. Прежде, чем  тестировать приложение, добавьте еще импорт папки levels в класс Editor.as строчкой:

import com.towerdefence.levels.*;

Но и после этого не спешите запускать игру. Надеюсь, вы помните, что у нас _mapMask в классе Universe.as является приватной переменной и для нее мы реализовали ранее только геттер, то есть в данный момент мы не имеем возможности записать в нее что-либо из любого другого класса, но при этом мы уже написали в классе LevelBase.as строчку _universe.mapMask = _mapMask; поэтому сейчас нам нужно написать сеттер в классе Universe.as, чтобы все работало, как нужно. Открываем Universe.as для редактирования и добавляем новый сеттер:

public function set mapMask(value:Array):void
{
  if (value != null)
  {
    _mapMask = value;
    updateDebugGrid();
  }
}

Здесь все предельно просто. Но я хочу обратить ваше внимание на то, что мы не копируем карту проходимости из уровня, а исключительно сохраняем на него указатель в переменно _mapMask класса Universe.as. Метод updateDebugGrid() так же нужно обязательно вызывать сразу после смены карты проходимости, чтобы перерисовать сетку и сразу увидить изменения на экране.

Теперь запустите игру, выберите редактор, отредактируйте маску проходимости на ваше усмотрение, и нажмите кнопку «Save», после чего скопируйте содержимое окна Output (кроме версии приложения) и вставьте в конструктор класса Level1.as. Теперь вновь скомпилируйте игру и выберите редактор уровней. Если все сделано правильно, то вы увидите ранее нарисованный уровень. Вот такое вот незамысловатое сохранение карты проходимости у нас получилось.

Домашнее задание

Сегодня мы сделали много интересных изменений, но не все еще закончено. В качестве домашнего задания я предлагаю вам реализовать загрузку уровней в игровом классе Game.as, добавив туда работу с LevelManager.as так же, как мы это сделали в Editor.as. Так же не забудьте убрать из Editor.as перехватчик нажатия клавиш, который создавал нового врага. И обязательно попробуйте создать второй уровень, чтобы окончательно разобраться с сохранением уровней и с работой простого класса LevelManager.as.

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

Заключение

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

Работая с ArmorGames мне довелось видеть пару их игр в процессе разработке, и в этих самых играх я видел совершенно аналогичный подход к встроенным редакторам уровней. Аналогичный выбор редактора уровней или игры при запуске приложения, так же кнопку экспорт уровня в окно Output в редакторе уровней. Что, в итоге может говорить об одном — описанный способ хранения уровней и подход к разработке редактора уровней очень даже оправдан и успешно справляется со своими задачами ;)

Кстати, может быть кто-то не в курсе, но скомпилированный *.swf файл игры вы можете легко отдать вашему дизайнеру уровней для работы над уровнями. Чтобы увидеть экспортируемый уровень в окне Output достаточно запустить эту флешку непосредственно в самой Flash IDE, и при нажатии на кнопку «Save» вы сможете сохранить свои результаты, скопировав их из Output и вставив, например в текстовый документ, который можно переслать программисту, чтобы тот встроил уровень уже непосредственно в игру. Конечно, в таком случае очень полезной будет функция тестирования уровня без его сохранения, благо со встроенным редактором уровней такую функцию достаточно легко реализовать.

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

Ссылка на исходники — CS4, *.zip, 118 кб.

P.S.: Сильно извиняюсь, что стал немного реже писать в блог. Очень увлекся новым проектом и дни пролетают, как один. Но я стараюсь исправиться и уже записал в планировщик напоминание о следующем посте :)

Система Orphus

Написать комментарий
Индикаторы: Уроки, Action Script 3
Постоянная ссылка

 

Антон, спасибо Вам большое за уроки. Очень надеюсь что Вы не бросите их.

Denis K.
20 Февраля 2011
— 17:01
#

Отличные уроки! СПАСИБО!

tipugin
20 Февраля 2011
— 17:50
#

третий наф! фан! лав!
робокоп мертв! роботроль жив)))

ant
20 Февраля 2011
— 18:50
#

Антон, спасбо огромное. Наконец я дождался) Счастье - это просто)

Алексей
20 Февраля 2011
— 18:58
#

Разбирался в данном вопросе сам, очень помогли первые записи в твоем блоге. В итоге после недели тщательного изучения материала в голове начала вырисовываться картина, как все должно работать. Для новичка это достаточно сложно. Поэтому для всех тех, кто только начал програмить, особенно важно использоваться полученные знания на практике, причем сразу!!!

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

Антон, Огроменное тебе спасибо!!! +++

Вадим М.
20 Февраля 2011
— 19:36
#

Очень познавательно, спасибо!
Только кажется это уже #6

nigmashumma
20 Февраля 2011
— 20:19
#

А я думал пойдет речь об AIR :).
А XML не так страшен как его малюют, я свой редактор пишу на другом языке (GML) и сохраняю в XML в игру вставляю в TextField, из которого все и загружается...
Спасибо за урок, мне его не хватало.

WeslomPo
20 Февраля 2011
— 20:29
#

Антон, Спасибо за урок!
P.S. Номер урока нверно 6-ой а не 5-ый. Так как 5-ый был "Поиск пути".

Daniil S.
20 Февраля 2011
— 20:36
#

Антон, а в CS3 больше не будет исходников?

Алексей
20 Февраля 2011
— 21:00
#

А, они все были только в CS4, пардон.

Алексей
20 Февраля 2011
— 21:02
#

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

jarofed
20 Февраля 2011
— 21:11
#

@Denis K. уроки бросать не планирую, во всяком случае ваша активность в коментариях показывает, что это вам нужно и поэтому буду стараться продалжать в том же духе ;)

Ant.Karlov
20 Февраля 2011
— 21:29
#

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

Ant.Karlov
20 Февраля 2011
— 21:36
#

@nigmashumma и @Daniil S. Урок действительно уже #6 авто заполнение в браузере поставило старую циферку, а я не обратил внимание. Поправил. Спасибо!

Ant.Karlov
20 Февраля 2011
— 21:39
#

@WeslomPo, внешний редактор на другом языке - это еще не так жестоко как xml в TextField. Xml данные в формате xml можно напрямую присвоить в переменную типа XML, например так же как в данном уроке мы сохраняем карту проходимости в массив и работать потом с ней как с xml объектом. В общем советую почитать мануалы и поискать примеры :)

Ant.Karlov
20 Февраля 2011
— 21:47
#

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

Ant.Karlov
20 Февраля 2011
— 21:52
#

@Ant.Karlov в TextField засунул, чтобы люди без компилятора могли бы запускать уровни для тестирования. Для этого, конечно, пришлось использовать конструкцию try -- catch.

WeslomPo
20 Февраля 2011
— 22:46
#

WelsomPro, не знаю, как в АС3, но в, PHP, например, блоки try, catch жрут неимоверное кол-во памяти.

f-duck
20 Февраля 2011
— 22:49
#

Для рисования уровней дизайнером - пойдет. В игре уберу. :).

WeslomPo
20 Февраля 2011
— 23:19
#

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

tipugin
21 Февраля 2011
— 01:04
#

Для редактора - не проще ли файлреференсом сохранять прямо на диск?

Ну или для более продвинутых встроить в дельфовую или сишную оболочку либо собрать эйр приложение?

Platon
21 Февраля 2011
— 02:18
#

@WeslomPo, ааа... Я не понял фишки сразу. Ну тогда конечно, это тоже неплохое решение для быстрого тестирования :)

Ant.Karlov
21 Февраля 2011
— 09:50
#

@tipugin, да, подобная структура игры отлично подойдет для любого жанра и типа игр.

Содержание для уроков обязательно сделаю. Спасибо :)

Ant.Karlov
21 Февраля 2011
— 09:52
#

@Platon, можно и через fileReference сохранять. Я правда не пробовал этот способ, так как меня вполне устраивает выше описанный способ. Не знаю точно как работает fileReference, но мне в голову сразу приходит мысль о том, что в этом случае прийдется как-то уже организовывать работу с файлами: смотреть список созданных файлов, сохранять новые, загружать ранее сохраненные и все это в итоге влечет за собой много лишней работы.

Как-то давным давно я встраивал Flash в приложение написанное на Delphi и все это делалось тогда какими-то невероятно сложными методами с забиванием разных костылей. Может быть сейчас все стало намного проще, но в любом случае я считаю что подобные решения интересны и оптимальны только для гиков :)

Ant.Karlov
21 Февраля 2011
— 10:04
#

Антон, спасибо тебе, огромное!

scar4ace (Макс)
21 Февраля 2011
— 11:48
#

Спасибо, как всегда безумно интересно !

wil
21 Февраля 2011
— 12:01
#

Антон, а почему не используете более полноценные ide (вроде FDT)? И выносите ли весь медиа контент игры в swc? Или прямо в библиотеке оставляете?
Спрашиваю потому, что работал раньше с as3, использовал flashdevelop, и поэтому ресурсы приходилось организовывать с помощью SWC. Не очень удобно, когда часто чтото меняется.
Вообщем, хочется узнать какой способ хранения ресурсов считается более правильным? (или никакой разницы нет?)

tipugin
21 Февраля 2011
— 13:36
#

Спасибо.

Danny
21 Февраля 2011
— 15:47
#

Антон,
файлРеференс реализуется в 2 строчки:
var мyFile:FileReference = new FileReference();
мyFile.save(textString,"fileName.xml");

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

оболочка со встройкой флеша как эктив-х компонента - чуть сложнее, но зато сделать надо 1 раз, и не будет появляться это раздражающее окошко.
Исходники кстати могу скинуть на почту, если интересно.

Когда много приходится возиться, метод копипаста из панельки аутпута несколько утомляет :)

Platon
21 Февраля 2011
— 16:49
#

@tipugin, я работаю под MacOS и под него к сожалению нет версии FDT. Но при этом я и не пишу код в самом Flash IDE потому что это очень не удобно, для работы с кодом я использую замечательный TextMate. В самой же Flash IDE я рисую, анимирую, создаю уровни ну и соответственно компилирую игру.

Весь контент у меня хранится в рабочем *.fla проекте, в *.swc ничего не выношу. Несколько раз хотел попробовать хранить все *.swc чтобы ускорять процесс компиляции игры на финальных стадиях разработки, но так и не дошли руки. Тем более я достаточно много прямо по ходу написания кода могу править графику поэтому все равно все пришлось бы пересобирать.

Наиболее правильный способ хранения ресурсов тот который удобен вам ;) А с точки зрения программы я думаю, что с *.fla, что с *.swc все в итоге одинаково получается. Но не возьмусь однозначно утверждать, так как не интересовался этим вопросом.

Ant.Karlov
22 Февраля 2011
— 13:41
#

@tipugin ой, мне показалось что под FDT вы подразумеваете FlashDevelop, а потом я уже подумал почему там еще буква T? :)

FDT я даже не пробовал использовать. Пару раз открывал оффициальный сайт, смотрел видео и закрывал. Смущало бесконечное количество окошек которые там открываются и настраиваются в видео роликах. Я плохо понимаю зачем мне это все нужно так как *.as файлы — это же обычные текстовые файлы которые мне надо быстро и удобно редактировать :) И с этими функциями на ура справляется мой любимый TextMate.

Ant.Karlov
22 Февраля 2011
— 13:47
#

Кстати, я встречал еще один способ сохранения уровней. Сначала уровень записывается в виде строки XML, или JSON. Потом сжимается и выводится в виде небольшой строчки.

var tmp:String = JSON.encode(itemsArray);
var ba:ByteArray = new ByteArray();
ba.writeUTFBytes(tmp);
ba.compress();
var base64:String = Base64.encode(ba); ModalsController.instance.showSave(base64);

В несложной игре все уровни умещаются в 3-5 строк. Вывести можно прямо на экран плеера. А значит, что редактором смогут пользоваться как левел дизайнеры, которые могу не знать Flash IDE, так и обычные пользователи =)

1g0rrr
22 Февраля 2011
— 13:55
#

@Platon,

> но у него одно ограничение - он не может
> писать результат без показа окошка
> файлового броузера


Интересно, а если swf запущен в стэндалоне флеш плеере, будет окошко сохранения? За время разработки Mushroomer процесс копипаста уровней из output меня особо не утомлял :)

Спасибо за предложение посмотреть исходники. Но в формате уроков я думаю, что не стоит усложнять процесс сохранения уровней, главное чтобы просто и понятно все было. А гики и без туториалов найдут альтернативные решения :) Сам же я после Mushroomer`a пока больше новых навороченных редакторов уровней больше не писал и пока не вижу в этом необходимости для себя.

Ant.Karlov
22 Февраля 2011
— 13:55
#

@1g0rrr, спасибо за пример! Отличный способ. Так же он отлично подходит для хранения и загрузки уровней где-нибудь на сервере в базе данных :)

Ant.Karlov
22 Февраля 2011
— 13:59
#

И еще - я тоже какое-то время компилировал с помощью Flash IDE. Но потом выделил пару часов времени и всётаки подправил проект под FDT.

У этого способа компиляции есть один огромный плюс - скорость. Даже на начальных стадиях Flash IDE ооочень медленно всё собирает, по сравнению с mxmlc, который встроен в FDT. И эти паузы постоянно сбивали меня с мысли. Переход на FDT очень облегчил мне жизнь, т.к. я люблю пересобирать проект часто, для проверок =) А там он собирается буквально мгновенно.

В TextMate можно выставить компиляцию с mxmlc?

1g0rrr
22 Февраля 2011
— 14:03
#

@Ant.Karlov, согласен, у самого макос и textmate используется для всего что можно отредактировать. FDT, согласен - тяжелая штука (привет эклипс), но автодополнение кода очень полезная вещь, особенно когда работаешь с новыми либами

@1g0rrr, да были уроки про textmate+mxmlc. макросом вроде делается

tipugin
22 Февраля 2011
— 15:39
#

* Пардон, я когда писал - имел ввиду Flash Develop, а не FDT. FDT я тоже использовал. Неплохая штука, но да, запускается туго. И стоит дорого.

1g0rrr
22 Февраля 2011
— 16:38
#

Спасибо за уроки! Tower Defence - это вообще мой любимый жанр :) Так что такой урок для меня вдвойне полезен :)

ЗЫ Ради эксперимента попробовал пользоваться только Flash IDE, когда работал над уроками. Пришел к выводу, что работать в ней можно и в чем-то даже удобно, но иногда проскальзывают раздражающие, странные и нелогичные вещи, так что скорей всего вернусь к FlashDeveloper`у :)

Nikius
24 Февраля 2011
— 20:54
#

@Ant.Karolv: Редактор очень полезная штука, молодец что взялся писать об этом. В моем текущем проекте (я очень надеюсь что в марте мы все его сможем увидеть и оценить) редактор был сделан отдельно от самой игры. Почему? Да потому что игра может в любой момент упасть, похоронив тем самым все потуги геймдиза сделать уровень... Реально была такая вероятность. Так что имхо редактор отдельно - игра отдельно, вариант постабильнее. Но это уже на твоё усмотрение. По поводу текстфилда или еще чего - всё проще. Либо используем файл референс, либо не заморачиваемся и по кнопке SaveXML копируем всю инфу В КЛИПБОАРД, а не в текстфилд. Удобнее в стопицот раз)) А строчка кода для этого всего одна)) По поводу того как именно хранить уровни в XML - ну это творческая задача, тем не менее стоит хранить их именно только в нём, не прибегая к внедрению чего либо в код. Потому что тогда геймдиз может спокойно работать в отрыве от программера и клепать уровни. Очень удобно. По поводу волн и т.п. - это всё тоже в XML. И статистики монстров в т.ч. и статистики башен - всё там. Ну и рисование уровней (расстановка тайлов и путей) естественно тоже там. А на загрузке генерится из тайлов и доп.спрайтов общая картинка, никаких проблем не возникает с этим. Я надеюсь про редактор ты еще черкнешь пару статей - это очень нужная и полезная тема. Спасибо за то что продолжаешь эти уроки!

RaymondGames
26 Февраля 2011
— 21:49
#

@Ant.Karlov: Который раз жалею что ты не был на IndieSnow - в живую посмотрел бы мой редактор, сделал бы пару уровней, обменялись бы мнениями/взглядами/подходами/решениями проблем - бесценный опыт! (для меня точно!)

RaymondGames
26 Февраля 2011
— 21:56
#

@RaymondGames, я надеюсь, что у нас еще будет возможность посмотреть редактор и обменятся мнениями ;) Записи про редакторы уровней еще обязательно будут и не раз.

Про падения игр — я вообще наивно полагаю что стабильнее игры должны работать только программы управляющие ракетами и спутниками :) И в своей практике очень мало сталкивался с тем чтобы редакторы падали (правда я не делал возможно тестирования игры прямо из редактора :)). Больше всего неудобств возникало именно когда приходилось изменить формат данных. Кстати, Flash IDE тоже имеет свойство часто падать и как правило в самый неподходящий момнет. Поэтому поддерживаю — надо делать возможность сохранять уровень в клипбоард и повешать сохранение на хоткей, а потом приучить своего дизайнера уровней нажимать кнопку минимум 1 раз в 4-5 минут! :)

Ant.Karlov
3 Марта 2011
— 20:55
#

@Nikius, во Flash IDE можно только рисовать и анимировать, а вот с редактированием кода в IDE большая проблема! Если маленький прототип накидать еще можно, то полноценный проект там не осилить.

Ant.Karlov
3 Марта 2011
— 20:57
#

@1g0rrr,

> В TextMate можно выставить компиляцию с mxmlc?

Конечно можно, TextMate вообще супер-универсальный редактор там можно с чем угодно компиляцию выставить :) В нем можно писать код для любого языка программирования.

Кстати, у меня в бандле для TextMate под AS3 даже была готовая надстройка для компиляции в mxmlc, но где взять сам компилятор и как его установить — я не нашел. Быстрее оказалось научить TextMate посылать команду в Flash IDE чтобы тот компилировал текущий проект.

Ant.Karlov
3 Марта 2011
— 21:02
#

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

Makklaren
24 Марта 2011
— 14:53
#

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

Ant.Karlov
24 Марта 2011
— 21:39
#

я бы метод getLevel записал чуть по другому:
public function getLevel(levelID:int):LevelBase
{
var result:LevelBase;

if (levelID > 0 && levelID <= TOTAL_LEVELS)
{
var level:Class = getDefinitionByName("Level" + levelID) as Class;
result = new level() as LevelBase;
}
else
{
trace("LevelManager::getLevel() - Уровня", levelId, "не существует!");
}

return result;
}

Это избавляет нас от свича и необходимости дописывать классы.

Stepan
28 Марта 2011
— 16:54
#

А да забыл указать что в конструкторе LevelManager надо будет все таки прописывать сами классы иначе они просто не попадут во флешку и у вас будет эксепшен:


public function LevelManager()
{
Level1; // просто имя класса и больше ничего
}

Stepan
28 Марта 2011
— 17:53
#

Аааа, круто!!! очень понравился урок.
p.s. это первое домашнее задание, который смог самомстоятельно сделать, хаха) причем начала с кнопки "очистить уровень"

J0x
10 Апреля 2011
— 15:36
#

О, Антон, ты работал с ArmorGames? (:
В каком плане? D:

z3lf
13 Апреля 2011
— 06:03
#

@z3lf, с ArmorGames работал как художник. Участвовал в разработке Microboats и принял участие в разработке еще одной игры которая еще пока не вышла.

Ant.Karlov
14 Апреля 2011
— 18:27
#

Сори за тупой вопрос, но можешь вкратце объяснить, что означает Value и Set в твоем коде?И вообще?

1 private var _isEditor:Boolean = false;
2
3 public function set editorMode(value:Boolean):void
4 {
5 _isEditor = value;
6 }

Просто раньше я с таким не сталкивался)

Randy
30 Апреля 2011
— 22:30
#

@Randy, set в описании метода означает что это метод сеттер (setter). обращение к такому методу происходит как к переменной и в этом методе обязательно должен быть один входной параметр. В нашем случае это переменная value типа Boolean.

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

myClass.editorMode = true;

У сеттеров есть аналог: геттер (getter), описывается он аналогичным кодом, только в место set пишется get и геттер не может иметь входного параметра, но обязательно должен что-то возвращать, например:

public function get editorMode():Boolean
{
return _isEditor;
}

if (myClass.editorMode)
{
trace("Это редактор.");
}

Ant.Karlov
30 Апреля 2011
— 23:36
#

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

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

Randy
1 Мая 2011
— 08:48
#

Большое спасибо за Ваши уроки, нам новичкам очень помогает :) с домашним заданием справился, все очень доступно обьясняется.

P.S.
Интересно а я в свои 22 не имея никакого опыта программирования смогу этому научиться ? не позно ли я за это взялся ?

Marik
24 Июня 2011
— 19:05
#

@Marik, хороший у вас вопрос — я так и не придумал как на него не обидно и весело ответить, поэтому просто улыбаюсь :) А если серьезно, то учится никогда не поздно, главное желание и стремление ;)

Рад что уроки вам помогают.

Ant.Karlov
24 Июня 2011
— 21:33
#

@Marik:
Кен Сильверман начал изучать программирование еще в детстве (первый компьютер ему купили родители в 8 лет, а уже в 18 он работал над Build Engine в Apogee). Этот человек гений и вам его не догнать никогда, хотя бы потому что он все время идет вперед. Но ничто не мешает вам и в 22 года встать в длинную очередь позади этого человека и начать работать над собой. Хуже точно не будет.

BuxomBerry
25 Июня 2011
— 09:45
#

Спасибо за ответы ребята, мне то это нравится, просто сколько читал блоги в основном все или еще со школы колупались во флеше, либо пришли сюда из других языков программирования, вот я немного и засомневался. Спасибо что поддержали, буду стараться.

Marik
25 Июня 2011
— 22:44
#

конеш только практика иначе никак

еЁ
28 Октября 2011
— 20:48
#

Огромное спасибо вам, Антон!
Статья просто бесценная, настолько простое эффективное решение. То что называется ясность мысли.

А как бы вы посоветовали запоминать катсцены между уровнями?
* В LevelManager (так же как последовательность уровней)?
* в самом уровне завести (в BaseLevel) свойства типа intro, happyend, failure: MovieCLip и присваивать им значения - Библиотечные Символы - в потомках Класса?
* просто жестко "вшить их в ткань" уровня раз и навсегда?

ungooglable
14 Апреля 2012
— 14:37
#

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

Я второй день пытался понять, как сделать выбор уровня и, собсвтенно, как задавать последовательность уровней. (И это при том, что больше недели потратил на то, чтобы сделать сами уровни, и, казалось, все проработал)

Наконец, нашел эту замечтаельную статью, но перед тем как, разбираться, "Дай," - думаю, - "выпишу конкретные вопросы, сформулирую, что не ясно". Выписал. Набросал корявое решение, которое можно сделать просто сразу... Переписал, подправил... На третьей итеррации отложил и стал читать статью. Оказалось, что решение по сути совпадает с тем, что тут приведенно (только без LevelManager-а и чуть менее чисто)

Отсюда, мораль: если бы два дня назад четко сформулировал проблему, сэкономил бы эти два дня.

ungooglable
14 Апреля 2012
— 14:52
#

А еще можно не прописывать последовательность уровней жестко 1й, 2й, 3й, а сделать метод

LevelManager.getLevelByName(ID:string): Level

(устроен так же, как getLevel в статье) и массив

LevelManager.sequence: Array = ["Tutorial Level", "Level One", "Level Three", "The Final"];

а getLevel просто вызывает
return getLevelByName(sequence[_levelNumber])

Тогда если захочется убрать или вставить уровень, достаточно просто отредактировать список в sequence, а не менять case всем последующим уровням

ungooglable
14 Апреля 2012
— 15:01
#
 

 

Вы можете использовать следующие тэги для форматирования текста в комментариях: [b]толстый[/b], [i]наклонный[/i], [u]подчеркнутый[/u], [link]ссылка[/link], [link=адрес ссылки]текст ссылка[/link]