TowerDefence #12. Загрузка вражеских волн

В сегодняшнем уроке мы разберем домашнее задание из прошлого урока и реализуем загрузку вражеских волн из XML файлов.

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

  • Для каждого врага своя цель;
  • ?ли одна цель для всей вражеской волны;

Так вот, тогда, когда я написал тот урок, я был уверен в том, что идеально сделать цель для волны целиком, а не для каждого отдельного врага. Потому что во втором случае будет дополнительная рутина при создании вражеских волн. Согласитесь, задавать каждому врагу свою цель достаточно утомительная процедура, тем более если на уровне может быть 20 и более волн со среднем количеством врагов боле 10 шт. на волну. Но с тех пор прошло много времени и я изменил свою точку зрения, в частности этому поспособствовал Jelly Defence, в который я поиграл наконуне и оценил весь тот беспредел, который можно устроить, позволив задавать для каждого врага свои маршруты свои цели :) Одним словом, легких путей я не ищу. Но чтобы избежать рутины с прописыванием для каждого врага своей цели, мы реализуем оба варианта: одна цель для всей волны с возможностью задать отдельным врагам свои цели. То есть, если у врага нет своей уникальной цели, то он использует заданную цель для волны — думаю такой подход дает более креативные возможности для моделирования уровней, а так же избавляет от рутины.

Разбор домашнего задания

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

private var _respawnPoint:Avector = new Avector();
private var _globalTarget:Avector = new Avector();

Следом добавляем пару публичных методов для установки значений этих переменных:

public function setRespawnPoint(tx:int, ty:int):void
{
  _respawnPoint.set(tx, ty);
}

public function setTargetPoint(tx:int, ty:int):void
{
  _globalTarget.set(tx, ty);
}

После этого модифицируем метод addEnemy() следующим образом:

public function addEnemy(kind:uint, count:int,
  respawnInterval:Number, uniqueTarget:Avector = null):void
{
  _enemies[_enemies.length] = { kind:kind, count:count,
    respawn:respawnInterval, uniqueTarget:uniqueTarget };
}

Здесь мы добавили новый атрибут uniqueTarget, который отвечает за уникальную цель для каждого врага и по умолчанию он равен null. Теперь в методе nextEnemy() нужно добавить поддержку нового параметра. Найдите строку:

_enemy = { kind:o.kind, count:o.count, respawn:o.respawn };

? добавьте в нее uniqueTarget:

_enemy = { kind:o.kind, count:o.count, respawn:o.respawn, uniqueTarget:o.uniqueTarget };

Следом, в этом же методе нам нужно передать все эти параметры создаваемым врагам, поэтому давайте найдем строчку:

_universe.newEnemy(_enemy.kind);

? заменим её на такой вот код:

// Враг имеет уникальную цель, используем её
if (_enemy.uniqueTarget != null)
{
  _universe.newEnemy(_enemy.kind, _respawnPoint, _enemy.uniqueTarget);
}
else
// ?наче враг не имеет уникальной цели, используем глобальную
{
  _universe.newEnemy(_enemy.kind, _respawnPoint, _globalTarget);
}

Тут все просто, если уникальная цель не равна null, то значит отправляем нового врага на его специальное задание в уникальную точку. Ну, а если у него нет уникальной цели, то пусть отправляется на общее задание волны. Но не спешите компилировать игру, метод _universe.newEnemy() еще не знает о наличии двух дополнительных параметров, которые мы в него уже передаем, давайте его усовершенствуем. Открываем класс Universe.as, находим метод newEnemy() и первым делом меняем его старое объявление на новое:

public function newEnemy(kind:int, respawn:Avector, target:Avector):void

Находим в методе код инициализации нового врага:

enemy.init(startPos.x, startPos.y, finishPos.x, finishPos.y);

? заменяем его на то, что передается в метод:

enemy.init(respawn.x, respawn.y, target.x, target.y);

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

var wave:EnemyWave = new EnemyWave();
wave.startDelay = 10;
wave.setRespawnPoint(1, 1);
wave.setTargetPoint(14, 5);
wave.addEnemy(EnemyBase.KIND_JEEP, 15, 6);
wave.addEnemy(EnemyBase.KIND_SOLDIER, 20, 2);
_waves.push(wave);

wave = new EnemyWave();
wave.startDelay = 30;
wave.setRespawnPoint(18, 13);
wave.setTargetPoint(5, 5);
wave.addEnemy(EnemyBase.KIND_JEEP, 14, 6);
wave.addEnemy(EnemyBase.KIND_TANK, 13, 12);
wave.addEnemy(EnemyBase.KIND_JEEP, 12, 6);
_waves.push(wave);
_waveIndex = 0;

Единственный недостаток такого подхода к инициализации стартовой и финишной точки — это невозможность их указания из редактора уровней, то есть чтобы задать стартовую и финишную точки для текущих тестовых волн мне пришлось считать тайлы вручную :) Но это можно усовершенствовать. Мне кажется, что наиболее универсальным способом для связи стартовых и фишинных тайлов с вражескими волнами — это задавать ключевым тайлам уникальные имена. Например, в редакторе уровней мы создаем инструмент, который создает ключевые тайлы на игровой карте и который запрашивает ввести для них имя, отмечаем тайл, вводим для него текстовое имя и при экспорте уровня в окно output отдельным списком экспортируем ключевые тайлы:

[ "startPos1", 5, 5 ]
[ "finishPos1", 18, 13 ]
// где "имя тайла", коордианата x, коордианата y
// и т.п.

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

Теперь настало самое время протестировать наши нововведения в деле. Компилируем игру и видим, что теперь враги появляются в строго отведенном для волны месте и шагают дружным строем в поставленную цель. Более того, в методе инициализации тестовых волн вы можете попробовать задать отдельным единицам свои уникальные цели, дописав в методе _wave.addEnemy() новый параметр:

wave.addEnemy(EnemyBase.KIND_TANK, 1, 12, new Avector(10, 13));

Этот танк получил особое указание двигаться в сектор [10,13]! :) Кстати, обратите внимание, что мы можем задавать сразу пачке единиц свои цели — для этого лишь надо изменить второй параметр, который отвечает за количество. Правда здорово?!

Загрузка вражеских волн из XML файлов

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

Если вы совсем не знаете, что такое XML, то рассказываю в кратце: XML — это универсальный текстовый формат, позволяющий работать с иерархическими данными. Синтаксис XML визуально схож с html и благодаря своей наглядности и удобности распространен настолько, что даже во Flash есть стандартные классы и методы для работы с этим форматом, что сильно упростит нам жизнь. Но стоит отметить и недостаток этого формата — разбор большого XML файла может занимать время, а следовательно вызывать лаги в работе приложения при загрузке и парсинге XML файла. Так же при возникновении опечаток или некорректного формирование структуры XML файла (пропущен закрывающий тэг, или угловая скобочка), не исключены и ошибки, а вместе с ошибками и падения приложения если вы не обрабатываете эти ошибки в коде вашей программы. То есть, благодаря всей этой наглядности и удобству, со своей стороны нам тоже придется принести кое-что в жертву.

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

Структура XML файла

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

<?xml version="1.0" encoding="utf-8"?>
<levelData>
    <waves>
        <wave startDelay="10" respawnX="1" respawnY="1" targetX="14" targetY="5">
            <enemy kind="jeep" count="5" interval="6"></enemy>
            <enemy kind="soldier" count="20" interval="2"></enemy>
        </wave>
        <wave startDelay="30" respawnX="18" respawnY="13" targetX="5" targetY="5">
            <enemy kind="jeep" count="14" interval="6"></enemy>
            <enemy kind="tank" count="6" interval="12"></enemy>
            <enemy kind="tank" count="3" interval="12" targetX="10" targetY="13"></enemy>
            <enemy kind="tank" count="6" interval="12"></enemy>
            <enemy kind="jeep" count="12" interval="6"></enemy>
        </wave>
    </waves>
    
    <!-- здесь могут быть другие данные для уровня -->
    
</levelData>

?зучите подробно содержимое получившегося XML и сравните его с кодом, который мы писали ранее в конструкторе класса Universe.as:

var wave:EnemyWave = new EnemyWave();
wave.startDelay = 10;
wave.setRespawnPoint(1, 1);
wave.setTargetPoint(14, 5);
wave.addEnemy(EnemyBase.KIND_JEEP, 15, 6);
wave.addEnemy(EnemyBase.KIND_SOLDIER, 20, 2);
_waves.push(wave);

wave = new EnemyWave();
wave.startDelay = 30;
wave.setRespawnPoint(18, 13);
wave.setTargetPoint(5, 5);
wave.addEnemy(EnemyBase.KIND_JEEP, 14, 6);
wave.addEnemy(EnemyBase.KIND_TANK, 6, 12);
wave.addEnemy(EnemyBase.KIND_TANK, 3, 12, new Avector(10, 13));
wave.addEnemy(EnemyBase.KIND_TANK, 6, 12);
wave.addEnemy(EnemyBase.KIND_JEEP, 12, 6);
_waves.push(wave);

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

Сохраните содержимое XML файла, как Level1.xml в папку Sources, то есть именно туда, где находится наш TowerDefence.swf и перейдем к реализации загрузки XML файла.

Загрузка XML

Поскольку XML файлы хранят информацию о вражеских волнах, а в будущем возможно и любую другую информацию, которая относится к конкретному уровню, то нам следует сделать работу с XML файлами универсальной для всех уровней, то есть создадим базовые методы работы с XML в классе LevelBase.as, от которого наследуются все наши классы уровней. Открываем LevelBase.as для редактирования и добавляем импорт классов:

import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;

Далее создаем пару внутренних переменных:

// Данные XML
protected var _xmlData:XML;
// Загрузчик XML файлов
protected var _xmlLoader:URLLoader;

Теперь создадим метод, который будет выполнять загрузку XML документа из файла loadXML():

public function loadXML(fileName:String):void
{
  // Создаем загрузчик
  _xmlLoader = new URLLoader();
  // Выполняем запрос на загрузку файла
  _xmlLoader.load(new URLRequest(fileName));
  // Устанавливаем обработчик события на завершение загрузки файла
  _xmlLoader.addEventListener(Event.COMPLETE, xmlLoadCompleteHandler);
}

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

protected function xmlLoadCompleteHandler(event:Event):void
{
  _xmlData = new XML(event.target.data);
  trace(_xmlData);
}

Полученные текстовые данные после завершения загрузки преобразуются в XML и для теста мы выводим их в окно output — это нам сейчас нужно, чтобы проверить, как работает наш код. Теперь откройте класс нашего единственного уровня Level1.as и в конструкторе класса добавьте строку:

loadXML("Level1.xml");

Скомпилируйте приложение и посмотрите на результат. Если в окне output появилось содержимое нашего XML файла, то значит пока все идет так, как нам надо :)

Разбор загруженного XML

Теперь нам предстоит сделать самое интересное — это извлечь нужные нам значения из загруженного XML файла. Сейчас мы загружаем XML файлы, но ведь потом данные в XML формате будут у нас храниться прямо в коде, поэтому нам следует сделать разбор XML данных в отдельном методе. Допишите в классе LevelBase.as в методе xmlLoadCompleteHandler() вызов метода readXML(), и давайте сразу перейдем к его написанию:

protected function readXML():void
{
  // Количество вражеских волн на уровень
  var numWaves:int = _xmlData.waves.length() + 1;
  // Количество врагов в текущей волне
  var numEnemies:int = 0;
  // Переменная для новой волны
  var wave:EnemyWave;
  // Указатель на текущую волну в xml документе
  var xmlWave:XML;
  // Указатель на текущего врага в xml документе
  var xmlEnemy:XML;
  // Уникальная цель для отдельных врагов
  var uniqueTarget:Avector = null;
            
  // Перебираем все волны
  for (var i:int = 0; i < numWaves; i++)
  {
    // Сохраняем указатель на текущую волну
    xmlWave = _xmlData.waves.wave[i] as XML;
                
    // Создаем новую волну и устанавливаем данные из xml
    wave = new EnemyWave();
    wave.startDelay = int(xmlWave.@startDelay);
    wave.setRespawnPoint(int(xmlWave.@respawnX), 
      int(xmlWave.@respawnY));
    wave.setTargetPoint(int(xmlWave.@targetX),
      int(xmlWave.@targetY));
                
    // Добавляем врагов из xml в волну
    numEnemies = xmlWave.*.length();
    for (var j:int = 0; j < numEnemies; j++)
    {
      // Сохраняем указатель на текущего врага
      xmlEnemy = xmlWave.enemy[j] as XML;
                    
      // Проверка наличия уникальной цели у текущего врага
      if (int(xmlEnemy.@targetX) != 0 &&
          int(xmlEnemy.@targetY) != 0)
      {
        uniqueTarget = new Avector(int(xmlEnemy.@targetX),
          int(xmlEnemy.@targetY));
      }
      else
      {
        uniqueTarget = null;
      }
        
      // Добавляем врага в волну        
      wave.addEnemy(getKind(xmlEnemy.@kind), 
        int(xmlEnemy.@count), int(xmlEnemy.@interval), 
        uniqueTarget);
    }
                
    // Добавляем волну в игровой мир
    _universe.addWave(wave);
  }
            
  // Загрузка уровня завершена
  onLoadingFinish();
}

Код получился большим, но если приглядеться внимательно, то все не так страшно и запутанно, как может показаться на первый взгляд. В начале мы объявляем все временные переменные, которые нам понадобятся для разбора XML данных, потом переходим к первому основному циклу, который перебирает все волны, имеющиеся в XML документе. Внутри этого цикла мы создаем новый экземпляр класса вражеской волны с полученными данными из XML документа, а так же выполняем вложенный цикл, который перебирает всех вложенных врагов в текущую волну, и на основе их данных добавляет в только что созданную волну врагов. После этого цикла мы добавляем созданную волну с врагами в игровой мир. Когда все циклы выполнены волны с врагами разобраны и добавлены в игровой мир, мы вызываем метод onLoadingFinish() — этот метод позволит игровым уровням узнать, что загрузка уровня завершена и следует начинать игру. Метод onLoadingFinish() будет объявлен в классе LevelBase.as и останется пустым, а в уровнях потомках мы будем перекрывать этот метод.

protected function onLoadingFinish():void
{
  // Этот метод оставим пустым.
}

Еще, если вы помните, в XML документе мы задаем тип врага текстовым именем для нашего удобства, хотя в коде типы врагов у нас обозначаются числовыми значениями, и если вы внимательно разглядели код разбора XML данных, то могли заметить там использование метода getKind(), в который передается текстовое значение атрибута kind полученного из XML — этот метод возвращает числовое значение типа врага и выглядит он так:

public function getKind(kindName:String):int
{
  switch (kindName)
  {
    case "soldier" : return EnemyBase.KIND_SOLDIER; break;
    case "jeep" : return EnemyBase.KIND_JEEP; break;
    case "tank" : return EnemyBase.KIND_TANK; break;
  }
            
  return EnemyBase.KIND_SOLDIER;
}

Надеюсь тут все понятно. Помимо метода getKind() сейчас мы еще обращаемся к несуществующему методу addWave() для игрового мира Universe. Давайте откроем Universe.as и добавим метод addWave():

public function addWave(wave:EnemyWave):void
{
  _waves[_waves.length] = wave;
}

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

public function clearWawes():void
{
  var n:int = _waves.lenght;
  for (var i:int = 0; i < n; i++)
  {
    _waves[i] = null;
  }
  _waves.length = 0;
}

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

Вроде все готово и уже не терпится протестировать игру в очередной раз. Но не спешите. Загрузка и обработка уровня может занять какое-то время, и во время загрузки документа программа продолжит выполнять свою работу. Таким образом, с момента когда был вызван метод loadXML("Level1.xml") может пройти некоторое время и игровой мир начнет свою жизнь до того, как вражеские волны будут в него добавлены. Поэтому нам следует сделать что-то вроде паузы, в течении которой игровой мир будет спать до тех пор, пока мы не попросим его проснуться и начать игровую жизнь. Эта пауза позволит нам быть уверенными в том, что пока загружается и обрабатывается игровой уровень, в игровом мире ничего не будет происходить раньше времени.

?гровой мир проснись!

Откроем класс Universe.as и добавим новую переменную:

private var _isStarted:Boolean = false;

А следом за ней два метода:

public function startGame():void
{
  if (!_isStarted)
  {
    _waveIndex = 0;
    _isStarted = true;
  }
}
        
public function stopGame():void
{
  if (_isStarted)
  {
    _isStarted = false;
  }
}

Теперь перейдем к методу enterFrameHandler() и обернем вызовы всех наших обработчиков в условие:

if (_isStarted)
{
  enemies.update(_deltaTime);
  towers.update(_deltaTime);
  bullets.update(_deltaTime);
  updateWaves(_deltaTime);
}

То есть, теперь игровой мир начнет свою жизнь только после того, как мы вызовем метод startGame() и закончит эту жизнь после вызова метода stopGame(). Поскольку теперь по умолчанию игровой мир у нас отключен, нам следует сделать его включение. Открываем класс Level1.as и перекрываем метод onLoadingComplete() следующим образом:

override protected function onLoadingFinish():void
{
  _universe.startGame();
}

Та-дааа! Можно протестировать работу игры. Если Flash вдруг вам выдал много ошибок, то убедитесь, что вы добавили все необходимые import'ы в классе LevelBase.as для используемых объектов. ? не забудьте удалить инициализацию тестовых волн из конструктора класса Universe.as.

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

Кстати, во время написания кода для этого урока я случайно указал точку появления и точку цели для вражеской единицы одинаковыми, и когда дело дошло до этого юнита то Flash Player намертво завис и отвесить его было не просто. Проблема заключалась в бесконечном цикле в классе PathFinder.as в методе getWay() список направлений получился нулевым и метод не мог построить путь. Подобная ошибка может возникнуть на этапе разработки из-за невнимательности разработчика, но все же даже подобного допускать не стоит, поэтому следует в метод getWay() добавить переменную errorCounter и каждую итерацию цикла увеличивать её значение на 1, а если значение привысило 1000 то кидать ошибку: throw new Error("Не могу построить путь.") — это во всяком случае лучше чем вешать плеер, возможно вместе с браузером :)

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

В качестве сегодняшнего домашнего задания я предлагаю вам подумать над тем, как можно сделать повторную загрузку данных из *.xml файлов без перезапуска Flash Player. Например, сделали какие-то изменения в файле Level1.xml, нажимаем горячую клавишу в игре или кнопочку где-нибудь на карте, и пусть уровень запускается сначала :)

Заключение

Сегодня мы вновь сделали большой шаг вперед: доработали вражеские волны, получив больше возможностей для их настройки, а так же сделали загрузку вражеских волн из XML файлов, что в будущем упростит создание игровых уровней, и более того настройку вражеских волн теперь можно спокойно поручить кому-то еще в вашей команде. Возможно вас все еще мучает вопрос о том, как встроить XML файлы непосредственно в игру — не переживайте об этом, содержимое XML документа достаточно присвоить переменной _xmlData в конструкторе класса уровня и сразу после этого вызывать метод readXML(). В общем просто загляните в исходники к данному уроку.

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

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

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

Результат

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

Содержание

  1. Вступление
  2. Структура игры
  3. Карта проходимости
  4. Первый враг
  5. Готовимся к поиску пути
  6. Поиск пути
  7. Редактор уровней
  8. Движение врагов
  9. Первая башня
  10. Кэширование объектов
  11. Полоса жизни
  12. Вражеские волны
  13. Загрузка вражеских волн
  14. Продолжение следует...

 

 

Спасибо снова!!!!А расскажите,у вас вдохновление по идеям игр ваших,или есть чуть-чуть плагиата?

Алексей
12 Декабря 2011
— 23:23
#

? кстате,заедьте,если вы еще на FlashGAMM2011 Kyiv,в Обухов,там есть рядом "Парк Киевская Русь".Заедьте,очень много интересного....может даже натолкнет мысль на новую игру.

Алексей
12 Декабря 2011
— 23:35
#

Большое спасибо за урок Антон! Вот рисую я сейчас текстуры для своей игры вторую неделю, думаю почему же меня в художку не отдавали в детстве((( и немножко завидую твоему таланту, в хорошем смысле этого слова. ? еще, большое тебе спасибо Антон за советы по покупке планшета, следуя им купил себе замечательный планшетик. Правда жену от него не оторвешь, оказывается с ним так удобно рисовать графити в контакте))).

aNI[526]
13 Декабря 2011
— 00:00
#

Спасибо за урок. С удовольствием прочитал, для меня особенно полезной была информация о работе с XML-файлами.

jarofed
13 Декабря 2011
— 14:49
#

@Алексей, посмотрите на игры и сами решите есть ли в них плагиата. В моих играх нет уникальных игровых механик, но при этом я стараюсь добавлять что-то от себя.

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

Ant.Karlov
13 Декабря 2011
— 14:51
#

@aNI[526], я думаю что талант — это некое уникальное представление чего-либо, а вот чтобы реализовать это представление в жизнь, нужна практика. То есть даже не смотря на то что я ходил в детстве в художку, мне все равно приходится много экспериментировать и пробовать новые подходы и только благодаря долгим мучениям получается что-то интересное и похожее на то что я себе представляю. Чем больше рисуешь и чем больше экспериментируешь, тем лучше становится результат.

Кстати, на планшете удобно не только рисовать, еще на нем отлично играется в игры, особенно в те где нужно переставлять гемы местами или много стрелять ;) В свое время планшет меня сильно выручал в игре Plants vs Zombie — это когда еще небыло iPad`ов на белом свете.

Ant.Karlov
13 Декабря 2011
— 14:57
#

Здравствуйте, Антон!
Прошу прощения за оффтоп, т.к. вопрос касается Вашего раннего поста "Постмортем разработки игры Zombotron". В "Скролле игрового мира" Вы затронули такую тему как нарезка и растеризация уровня на кусочки. Не подскажите какими средствами можно добиться реализации данной вещи? ?ли, может быть, подкините какие-нибудь источники, где можно почерпнуть данную информацию? Заранее благодарен за ответ!

Rise
13 Декабря 2011
— 18:20
#

Присоединись к выше заданному вопросу)

Kudesnik680
15 Декабря 2011
— 13:12
#

Маленькие мелочи:
1)
Открываем класс Level1.as и перекрываем метод onLoadingComplete()
Конечно имелось ввиду onLoadingFinish.

2)
Немного не синхронизирована функция clearWawes в исходниках и статье.

3)
В статье код функции xmlLoadCompleteHandler не снимает обработчик. В исходниках все хорошо. _xmlLoader.removeEventListener(Event.COMPLETE, xmlLoadCompleteHandler);

4) В исходниках ObjectController.as убрать аргумент у функции clear


P.S.
Не скажу, чтоб домашнее задание получилось самым легким ;) Но зато освежил весь игровой механизм + никто "легко" не обещал (c)
В обработчик нажатия клавиш файла Game.as необходимо добавить:
_universe.stopGame();
_universe.clearWaves();
_currentLevel = _levelManager.getLevel(1);
_currentLevel.load();

Кроме того, обновить код функции stopGame() - почистить контролер, иначе старые пули, башни и враги останутся на экране.
towers.clear();
enemies.clear();
bullets.clear();

Кроме того, обновить код функции clearWaves() - почистить указатель текущей вражеской волны.
_currentWave = null;

Спасибо за качественный урок! Огромное спасибо за Ваш игролог!

FirstFlashGame
15 Декабря 2011
— 17:29
#

Здравствуйте Антон, подскажите пожалуйста.
Нужно реализовать взрыв, только на JavaScript в канвасе. Летит снаряд и достигает земли(земля нарисована дизайнеров в png). При касании снаряда с землей происходит взрыв. Физику я реализовал, но проблема в том, что мне нужно сделать впадину/углубление после взрыва. Я думаю получить все пиксели, которые находятся в картинке(земля.png) и удалять/alpha=0 их. Каждый раз я буду удалять их в разном порядке, чтоб воронка после взрыва не была похоже на предыдущую, а так же буду учитывать силу попадания снаряда.
Что вы скажете о моем методе? Не сильно будет грузить память? Может предложите другой вариант? Знаю, что с ActionScript не связанно, но я имею опыт(3+ лет) разработке на флеш, так что можете посоветовать реализацию на флеш, которую можно преобразовать в JavaScript.
Заранее благодарен

Isaac
16 Декабря 2011
— 19:07
#

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

Ant.Karlov
17 Декабря 2011
— 11:25
#

@FirstFlashGame, спасибо за список замечаний. Создание уроков достаточно трудоемкая задача и такие небольшие несовпадения конечно случаются :) Молдец что сделали домашнее задание!

Ant.Karlov
17 Декабря 2011
— 11:26
#

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

Ant.Karlov
17 Декабря 2011
— 11:36
#

А что вы имеете в виду под маской проходимости и для чего она нужна в данном случае?

Isaac
17 Декабря 2011
— 19:06
#

Спасибо за информацию!
? конечно, думаю, очень многие бы были безумно рады, если бы Вы посвятили этой теме отдельную запись в блоге!

Rise
18 Декабря 2011
— 13:27
#

@Isaac, под маской проходимости я имею в виду некую карту по которой юниты могут определять где проходимые и не проходимые зоны. Если вы делаете попиксельно разрушаемый ландшафт, то скорее всего наиболее распространенная клеточная маска проходимости вам не подойдет и скорее всего в качестве альтернативы ва прийдется использовать изображение состоящие из двух цветов. Например: белый — проходимая зона, черный — непроходимая. Когда вы деформируете свой ландшафт, то одновременно вы так же деформируете и маску проходимости.

Ant.Karlov
20 Декабря 2011
— 13:37
#

Антон Спасибо за замечательный урок. Многое стало понятно. Можно 1 предновогоднее пожелание? =3

Пусть следующей темой игро-строя будет создания "Platform adventure games" пожалуйста.

Тема для идей: Lego Hero Factory - Creep Crusher

Ну и конечно-же: Zombotron ^...^`

С Наступающим =3

Crash
21 Декабря 2011
— 20:38
#

"Я пока решил пропустить этап создания прелоадера, так как это довольно скучная процедура и начинать с нее не особо интересно. Тем более на данном этапе в игре нет почти никаких ресурсов, поэтому прелоадер мы интегрируем в игру чуть позже."
Меня интересует, как именно с архитектурной точки зрения реализуется прелоадер? Это агрегат над классом App, или абсолютно самостоятельная компонента
не имеющая никак завязок на папку comtowerdefence. ? если это часть framework-a то как происходит кастомизация прелоадера для игр с разным визуальным оформлением?

FirstFlashGame
26 Декабря 2011
— 14:32
#

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

Кастомизация прелодера может происходить любым удобным образом: например в первом кадре нашей флешки мы можем разместить любой клип с прогрессбаром и нарисованными в нем кадрами, задать ему любое имя, после чего передать основному классу информацию о том что этот клип является прогресс баром setProgressBar("clipName"); - этот метод мы реализуем самостоятельно. После чего основной класс Preloader будет его использовать как индикатор процесса загрузки, так же можно сделать и программное рисования отображение процесса загрузки, тут уже кому как нравится.

Более подробно об этом я расскажу в ближайшее время.

Ant.Karlov
30 Декабря 2011
— 11:57
#

Спасибо, с нетерпением буду ждать новых уроков.

? еще маленький баг.
// Количество вражеских волн на уровень
var numWaves:int = _xmlData.waves.length() + 1;

возможно, имелось ввиду:

// Количество вражеских волн на уровень
var numWaves:int = _xmlData.waves.wave.length();

FirstFlashGame
30 Декабря 2011
— 17:51
#

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

Ant.Karlov
6 Января 2012
— 12:16
#

@Ant.Karlov
Прошу прощения, некорректно выразился: не баг а маленькая фича.

Хочу поинтересоваться Вашим мнением:
Для реализации движения снарядов по дуге, падения бомб с летящих юнитов… нужно использовать box2d/Nape, или возможно обойтись самопальным кодом для расчетов физики? Что будет более рациональным решением для TowerDefences начального уровня?

FirstFlashGame
6 Января 2012
— 22:34
#

Файл GunTower .c
Функция update()
case TowerBase.STATE_ATTACK:

Есть следующий код:
_head.rotation = Amath.getAngleDeg(this.x, this.y, _enemyTarget.x, _enemyTarget.y);
Таким кодом мы направляем пушку на врага.

Проявляется следующая фича:
?ногда дуло пушки дергается в совсем другую сторону от врагов.
А все дело в этом:
Мы сначала поворачиваем пушку, а уже потом выясняем не мертв ли враг, не убежал ли он, и дальше - стреляем. Вот с выстрелом и есть заковыка: пуля сразу не летит, она инициализируется. Только после выхода с функции towers.update(_deltaTime); следующим идет вызов bullets.update(_deltaTime); Здесь пуля и начинает свой полет, убивает врага, убирает его со сцены… При инициализации следующей волны этот же юнит вытягивается с кеша…А пушка об этом всем не в курсе, поэтому поворачивается в сторону респауна врагов (потому-что ее указатель _enemyTarget дальше тыкает на врага), и только после этого выясняет что враг как бы убежал от нее.

?зменить поведение можно разными способами, наиболее простой это передвинуть поворот башни поближе к функции shoot()

P.S.
Ну и еще раз хочу сказать спасибо! ?спользую Вашу архитектуру от данных уроков для своей первой игры. Очень доволен заложенным фундаментом!

FirstFlashGame
23 Января 2012
— 01:47
#

Антон, просто хотел сказать спасибо за то что вы делаете! Перечитал весь блог, подчерпнул очень много полезной информации! Ждем новых уроков! :)

RomHunter
14 Февраля 2012
— 13:27
#

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

Антон, а когда планируется выпуск новых уроков?

FirstFlashGame
23 Марта 2012
— 17:50
#

непонятно зачем точкам респауна и цели давать тип Avector? Мне кажется, это очень неэкономно, методы то все-равно не используются.

Роман
29 Марта 2012
— 17:26
#

@FirstFlashGame, это не так сложно, в своем первом тд уже реализовал) в классе Game создаем контейнеры для мувиклипов панельки управления и кнопок, на кнопки вешаем хэндлеры, которые переключают публичную переменную в классе universe, у меня это _currentTower:String;
а в методе постройки башен добавлена проверка этой строки, смотря чему соответствует - такую башню и строим) наверное, очень топорный метод, но я AS3 по этим урокам практически с нуля учил.

У самого такой вопрос: как бы переписать метод создания волн и xml так, что бы можно было задавать значение здоровья у юнитов? для большинства таких игр характерно увеличение этого показателя, поэтому логично сделать, скажем прирост в 10% относительно предыдущей волны. я пока голову ломаю) может, кто подскажет?

Евгений
15 Апреля 2012
— 22:34
#

@FirstFlashGame, новые уроки как и новые записи в блоге будут скорее всего не раньше чем в мае, после релиза Зомботрона. Очень много работы и на блог не остается сил и времени.

Ant.Karlov
16 Апреля 2012
— 00:15
#

@Евгений, про интерфейс все верно говорите. На флеше примерно так все это и делается. Бывают конечно всякие исключения и нюансы, но общая идея верная.

Что касается загрузки волн, то вам просто нужно в xml данные добавить для врагов еще один атрибут health который можно было бы задавать как для каждого врага отдельно, так и для волны целиком. А если в волне не указан параметр здоровья, то использовать здоровье по умолчанию. Кроме этого могу еще посоветовать вводить коэффициенты сложности которые влияют на значения здоровья. Таким образом сложность уровня можно шевелить меняя 1 или 2 параметра, а не переписывать значения у всех врагов во всех волнах. Например:

var difficultCoef:Number = 0.8;

someUnit.health = DEFAULT_HEALTH * difficultCoef;

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

Ant.Karlov
16 Апреля 2012
— 00:24
#

Елки-палки, промучался тут, для меня это, видимо, пока еще сложно :(
наверное, иду не тем путем, но тем не менее: создал публичную переменную difficultCoeff в EnemyWave, в LevelBase тоже прописал, в xml добавил и в самом классе EnemyWave она замечательно трейсится. А вот дальше дело никак не идет(( пытаюсь в EnemyBase прописать домножение _health на нее и ни в какую, стопудов неправильный способ, хотя честное слово, препятствий не вижу!) difficultCoeff ведь публичная, почему бы ей не разрешить использоваться в энемиБэйс. Так нет же, флеш пишет ошибку 1119 Access of possibly undefined property ... through a reference with static type Class.
чувствую прям, глобальной какой-то вещи не понимаю, что совсем по другому надо действовать, а как - не пойму)

Евгений
17 Апреля 2012
— 13:06
#

разобрался) все куда проще оказалось. нужно было расширить EnemyWave

Евгений
23 Апреля 2012
— 12:39
#

Спасибо за уроки!
Антон, в этом уроке в EnemyBase.as в function init - Вы вынесли addChild(_healthBar); из тела цикла, т.к. иначе healthBar у Танка не появлялся (у меня, начиная с 4-го Танка). У остальных врагов этого НЕ происходит! Объясните, почему так? В чем разница, где добавлять – внутри или вне цикла?

CherPack
3 Мая 2012
— 13:42
#

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

CherPack
3 Мая 2012
— 13:46
#

to CherPack
Между 10 и 12 уроками есть разница в способе добавления/удаления _healthBar. Два изменения:
1)
public function free():void
{
….

if (_universe.contains(_healthBar))
{
removeChild(_healthBar);
}


2)
public function init(posX:int, posY:int, targetX:int, targetY:int):void
{

if (_healthBar == null)
{
_healthBar = new UnitHealthBar_mc();
}

addChild(_healthBar);


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

FirstFlashGame
3 Мая 2012
— 14:53
#

to FirstFlashGame
?зменения между 10 и 12 уроками я заметил!
Так в чем РАЗН?ЦА - добавлять в условии или нет?
Условие:
Если бара нет - создаем его
Затем - добавляем на сцену: addChild(_healthBar);
Он и добавляется, даже внутри условия, только у Танка, начиная с 4-го танка - добавление прекращается.
Почему?

CherPack
4 Мая 2012
— 14:32
#

@CherPack, если вы внимательно читали уроки вместе с домашним заданием, то должны были разобраться почему способ добавления полосы жизни изменился. Если не читали, то объясняю: изначально индикатор жизни прикреплялся к юниту с его рождения и все время был с ним до его смерти. Но большое количество индикаторов жизни явно портят эстетический вид игры и поэтому было решено не показывать полосу жизни если юнит полностью здоров. Таким образом теперь индикатор жизни инициализируется только тогда когда юнит получает первый урон.

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

Ant.Karlov
4 Мая 2012
— 17:58
#

@Ant.Karlov
Уроки я ВСЕ выполняю, начиная с первого, причем дописываю код, а не беру обновленный исходник. Про кэш тоже догадался.
Если индикатор жизни удаляется из юнита, то ПОЧЕМУ он не создается вновь? Ведь в условии прописано - Если бара нет - создаем его!
Точнее, он создается, только, если добавление на сцену ВНЕ тела условия.
Я новичок, но пытаюсь докопаться до сути, для этого и уроки.

CherPack
4 Мая 2012
— 18:56
#

to CherPack
Бар создается в ф-ии init. Создается один раз за все время жизни объекта. Даже когда объект возвращается в кэш и потом снова извлекается из него это уже не привод к повторному созданию бара, так как условие не срабатывает повторно.
При удалении объекта в кэш происходит удаления бара со СП?СКА ОТОБРАЖЕН?Я! Это не приводит к удалению экземпляра полосы жизны а всего лишь ПРЯЧЕТ(делает невидимой если хотите) ее!!! Сам бар живет и здравствует, указатель на него и дальше корректный и НЕ НУЛЕВОЙ. Поэтому при повторном заходе в ф-ию init условие повторно не исполняется для врага который ранее был уже создан а затем убит и повторно реанимирован к жизни.

FirstFlashGame
4 Мая 2012
— 22:19
#

to FirstFlashGame
Хороший ответ, теперь понятно.

CherPack
5 Мая 2012
— 03:03
#

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

FirstFlashGame
17 Мая 2012
— 16:23
#

@FirstFlashGame, обычно создается некий менеджер ачивок в который разные игровые объекты или игровые действия могут отправлять сообщения, например, что убит такой то враг, или игрок нашел очередную плюшку — сообщения такие можно отправлять в менеджер ачивок независимо от того была заработана ачивка ранее или нет, т.к. это уже контролирует непосредственно менеджер. (под сообщениями подразумевается не система событий, а, например, вызов метода в который передается идентификатор случившегося события). Сам менеджер ачивок содержит в себе все возможные счетчики и проверку на достижения условия для получения награды, если счетчик накрутил нужное количество для получение награды, то непосредственно сам менджер наград может создавать всплывающее окно с информацией о награде, либо отправлять сообщение другому классу чтобы тот сгенерировал визуальное уведомление игрока (зависит от архитектуры игры). Кроме этого менеджер ачивок должен уметь сохранять свое состояние в SharadObject на равне с игровыми сохранениями чтобы ачивки небыли привязаны к одной игровой сессии.

Пример:

public static const KILL_FIRST_ENEMY:uint = 1;
public static const KILL_100_ZOMBIES:uint = 2;

private var _killedEnemies:int = 0;
private var _killedZombies:int = 0;

public function someAction(actionId:uint):void
{
switch (actionId)
{
case KILL_FIRST_ENEMY :
_killedEnemies++;
if (_killedEnemies == 1)
{
giveAchievement(actionId);
}
break;

case KILL_100_ZOMBIES :
_killedZombies++;
if (_killedZombies == 100)
{
giveAchievement(actionId);
}
break;
}
}

private function giveAchievement(actionId:uint):void
{
// проверяем не получил ли игрок уже награду ранее, и выдаем награду, формируем визуальное уведомление
}

Ant.Karlov
18 Мая 2012
— 12:26
#

Реализовал ф-ию checkAchievement, которая по переданному ID ачивки проверяет наступления условия ее выполнения. Принадлежит к Universe.
Через XML описываются ачивки: строчный идентификатор, название, описание, числовая цель для зачисления прохождения ачивки. Грузится в Game.
Статистика счетчиков ачивок хранится в профайле игрока, сериализующегося в cookies

Спасибо за помощь!

FirstFlashGame
21 Мая 2012
— 11:24
#

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

Н?К?ТА
8 Июня 2012
— 08:39
#

Коли уже буде новий випуск?)

Roma_erdt
28 Августа 2012
— 19:04
#

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

Sm
8 Сентября 2012
— 23:38
#

Хотел бы спрость не меняет ли логику игры -TowerDefence`a дать возможность врагам стрелять по пушкам а то получаеться не много не честно:D.

Pavel
13 Октября 2012
— 00:05
#

@Pavel, логику меняет, но жанр остается прежний, ведь оборона по прежнему осуществляется средством башен ;)

Ant.Karlov
14 Октября 2012
— 01:58
#

Здравствуйте, очень интересует вопрос, будет ли продолжение?

Владислав
31 Октября 2012
— 14:06
#

@Владислав, продолжение будет, но немного позже.

Ant.Karlov
31 Октября 2012
— 17:55
#

Спасибо за познавательные статьи, но во всех примерах есть "ошибка".Можно ставить башни на пути врага и он стоит на месте спавна, на сколько я помню в 3 уроке были созданы разные ячейки поля(только для врагов/только для строительства и т.д.) но они как я понял не работают или я где-то ошибаюсь?

gonsh1k
2 Ноября 2012
— 09:30
#

@gonsh1k, да проблема того что башни можно ставить на пути врага — существует. Я все время забываю рассказать об этом. Не смотря на то, что мы разделили клетки по которым ходят враги и в которых мы привыкли строить башни, изначально идея заключалась в том, что игрок может строить башни как на дорогах, так и там где враги не проходят. Но при этом игрок не должен полностью закрывать проход. Как такое реализуется я расскажу в следующих уроках.

Ant.Karlov
3 Ноября 2012
— 00:09
#

Когда будет следующий урок по TowerDefence? Хотя бы примерные сроки очень жду.

gonsh1k
5 Ноября 2012
— 09:27
#

Спасибо! Одна из лучший серий уроков в интернете! Очень сильно помогает. Только вот уже год нету новых уроков, а очень бы хотелось посмотреть реализации некоторых моментов. (например код прелоудера) Которы вы обещли добавить в конце.

BolT
28 Ноября 2012
— 11:35
#

Здравствуйте, не поделитесь информацией о сроках выхода продолжения уроков?)

Crylin
18 Января 2013
— 18:10
#

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

Ant.Karlov
19 Января 2013
— 23:31
#

С нетерпением ждем продолжения!!!

Владимир
5 Апреля 2013
— 12:37
#

Ну наконец-то я дочитал все уроки.
Ещё хочу.

Большое спасибо!
Реально одни из самых лучших уроков,
в других либо всё примитивно, либо всё время рассказывают про DirectX.

Напомню, на всякий о чём писать дальше:

- Прелоадер.
- Проверка на существования пути.
- Реализация Level`ов.
- ?нтерфейс.
- Звуки.

Fear_Factory
6 Апреля 2013
— 22:53
#

Автор, не забивай на эти уроки. Круто же.

Руслан
16 Июня 2013
— 13:18
#

Хотел спросить по поводу уроков.
В начале была схемка классов, из Loader. -> App. -> Game. -> World.

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

Еще раз спасибо за уроки, очень не терпится увидеть продолжение =)

?ван Cheshir
4 Июля 2013
— 23:43
#

Уважаемый Антон, ваши уроки просто превосходны, по-другому и не скажешь. Просты и понятны, как для новичков, так и для профессионалов.
Хочу задать один немного личный вопрос, интересно знать ответ: откуда вы черпаете вдохновение делать игры, писать код?
По себе - губит лень. сложно собрать волю в кулак, сесть и сделать, хотя и борюсь с этим. На начальных этапах, видя собственные убогие рисунки и отсутствие игры - вообще почти не могу сесть.
Может есть какие способы :)?
Как насчёт урока "Как вдохновить себя на коддинг" ;)?
В любом случае, об уроках могу сказать только хорошее. Не бросайте это, пусть и неблагодарное, но Дело с большой буквы.

Ярослав
11 Июля 2013
— 01:31
#

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

BolT
11 Июля 2013
— 11:51
#

@?ван Cheshir, вы желаете увидеть взаимосвязь всех классов друг с другом? Это конечно можно реализовать в рамках уроков (нарисовать карту классов). Но для сложных приложений это порой слишком сложно получается как для построения такой карты, так и для её понимания :)

Ant.Karlov
17 Июля 2013
— 07:59
#

@Ярослав,

> Хочу задать один немного личный вопрос, интересно знать ответ: откуда вы черпаете вдохновение делать игры, писать код?

Сложно сказать. Порой мотивировать себя очень сложно. Но часто помогает принцип: просто брать и делать что-нибудь, а потом вроде загораешься текущей задачей и становится интересно.

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

Еще можете поискать статью о "Принцип помидорок" — тоже какое-то забавное решение о том как делать много дел. В общем тема интересная, я подумаю о полноценной статье на эту тему в ближайшее время. Спасибо! :)

Ant.Karlov
17 Июля 2013
— 08:06
#

Надеюсь что в уроках "продолжение следует"?

Юра
18 Июля 2013
— 12:41
#

Уважаемый Антон . Большое вам спасибо за ваш труд.
Ваши уроки это кладезь рунета по созданию FLASH игр .Хочу задать вопрос . У вас есть в планах продолжение этой ветки?

2meerKat
28 Августа 2013
— 00:29
#

Жаль что забили на уроки.

юрий
11 Января 2014
— 02:40
#

Это единственный материал из сети который помог мне как то представить AS3, все остальное только теория, и никакого представления что где и для чего...

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

Антон
4 Августа 2014
— 16:18
#

Тут у нас в универе все ждут новых уроков :
^_^

Коляда
6 Августа 2014
— 18:52
#

Привет! Антон, пожалуйста возобнови уроки. Очень очень прошу!

Роман
30 Мая 2015
— 03:59
#

Спасибо, уроки супер!
Ждем продолжения с нетерпением!!!!

Павел
10 Июня 2015
— 19:53
#

Спасибо, уроки супер!
Ждем продолжения с нетерпением!!!!

zackie
21 Октября 2015
— 18:14
#

Вы действительно уверены в том что стоит продолжать эту серию уроков? Просто мне активно все намекают на то, что Flash уже никому не нужен — поэтому, собственно, я совсем сложил руки на предмет уроков.

Ant.Karlov
22 Октября 2015
— 10:58
#

Вы ведь сами всё еще используете Flash для создания игр, почему бы не делиться опытом? Если Flash никому не нужен, то почему люди 4 года комментарии оставляют, в ожидании продолжения статей? Flash определённо умирает в качестве баннеров и проигрывателя видео, но не думаю, что люди перестанут делать игры на этой платформе, в ближайшие лет 10.

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

Костя
2 Ноября 2015
— 12:41
#

@Костя, проблема в том, что я получаю противоречивые комментарии по поводу того нужен Flash и уроки по нему или нет. Таким образом я утратил энтузиазм и интерес к урокам.

Если есть группа людей которым интересны уроки — то давайте соберемся и организуемся как-то чтобы я видел что есть заинтересованные люди, собрался бы с силами и завершил бы эту серию уроков! Мне будет проще вкладывать силы и время в уроки если я буду точно знать что есть люди которым это действительно важно и нужно!

Ant.Karlov
3 Ноября 2015
— 11:52
#

@Ant.Karlov, привет. Твои уроки очень нужны. Я реально с помощью их начал серьезно учить AS3 и очень огорчился когда увидел что продолжения не будет. Я думаю немало людей (не один десяток) ждут продолжения этих уроков. Очень во многом помогли они мне. Не бросай это дело, таких ресурсов мало и они очень нам новичкам нужны.

Ярослав
6 Ноября 2015
— 01:32
#

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

Ant.Karlov
6 Ноября 2015
— 02:06
#

Хотелось бы продолжения "банкета"...

TNR
5 Декабря 2015
— 15:32
#

@Ant.Karlov, привет! Уроки действительно очень помогают) Читаю параллельно книжку Колина Мука, но тут все интереснее описывается и хочется побольше практических примеров :) Спасибо за уроки. Хотелось бы видеть продолжение ^^

Сергей
5 Декабря 2015
— 23:10
#

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

Виталий
17 Декабря 2015
— 19:08
#

Антон, твои уроки очень нужны, возобнови пожалуйста. ?менно благодаря твоему блогу я начал заниматься играми. Для кого то может быть флеш и умер, но для анимации и игровой графики он великолепен, а значит и actionscript 3.0 будет актуален. Я просто влюблен во флеш.

Роман
20 Января 2016
— 16:01
#