20 марта 2013

Flash, ответы на вопросы - 2

Прошло немло времени, и вот на подходе вторая тройка вопросов по Flash и ответов на них... Напомню, что здесь вообще происходит. Дело в том, что я решил попробовать в формате вопрос-ответ пообщаться с читателями. Пока диалог происходит скорее односторонний, потому что я затягиваю выпуск ответов... И вот сегодня решил исправиться! Ура! Я долго думал над количеством рассматриваемых вопросов в одном выпуске, но все же остановился на трех, подходящее число! Итак, сегодня у нас:
  • Embed шрифтов, полное описание на русском языке :)
  • как я делаю сайтлок
  • Загадочный DeltaTime
Все вопросы заданы Анонимным пользователем, за что ему огромное спасибо!
Embed шрифтов, полное описание на русском языке :)
Во всех играх используется текст. Он везде - на кнопках, в качестве игровых подсказок, в диалогах, на ачивментах, в названиях всяких окошек. Текст - один из способов общения разработчика с игроком. И, конечно, всегда хочется, чтобы шрифт был красивым, подходящим к самой игре. Поэтому стандартные шрифты я практически не использую... И вот возникает вопрос - создав текстовую надпись каким-то шрифтом с завитульками, как я могу быть уверенным, что он правильно отобразится у игрока, ведь у него такого шрифта в системе явно не установлено?

Начнем с того, что текст во Flash может быть статическим и динамическим.
  • о статическом тексте (static) можно не беспокоиться - разрабатывая во Flash IDE, среда сама все сделает, сняв эту головную боль с нас... даже, если у игрока нет шрифта для статического текста, он все равно будет корректно отображаться!
  • а вот для динамического (dynamic и input) текста нужно внедрить руками необходимый шрифт во флешку, иначе у игрока текст будет выводиться каким-нибудь стандартным шрифтом вроде Arial.
 
Но в чем вообще разница между статическим и динамическим текстом? Первый не может быть изменен, он вроде вообще внутри флеша как картинка воспринимается. А вот динамический текст может быть изменен во время игры. Такое текстовое поле обычно используется для вывода очков игрока, показа времени прохождения уровня, бегущего таймера, для ввода имени пользователя или других текстовых надписей, содержание которых неизвестно на момент сборки самой игры, оно может меняться, отсюда и название - динамический текст. Поле ввода (input text) - очевидно, тоже относится к динамически собираемому тексту.
Это мы говорим про создание текстового поля в Flash IDE, а вот в коде все выглядит до неприличия просто:

var fMoneyLabel: TextField = new TextField();
fMoneyLabel.text = 'You have earned 100 coins '

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

public class Preloader extends MovieClip
{
        [Embed(source = "/Users/Homw/Desktop/Projects/Fonts/Kristen.TTF", fontName = "MyKristen", fontWeight = "normal", embedAsCFF = "false")]
        protected static var EmbedFont:Class;
...
}

По порядку о передаваемых значениях:
  • source - путь к файлу
  • fontName - название шрифта (лучше использовать отличное, от названия шрифта в системе, я обычно добавляю префикс My, так он точно ни с кем не законфликтует)
  • fontWeight - стиль шрифта
  • embedAsCFF - исторический артефакт моего кода... работает и без указания этого параметра вообще...
Вот и все, теперь мы спокойно можем назначить надписи наш шрифт:

Format:TextFormat = new TextFormat();
Format.font = 'MyKirsten';
fMoneyLabel.defaultTextFormat = Format;
fMoneyLabel.embedFonts = true;

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


как я делаю сайтлок
Продав праймари-лицензию на игру, разработчик может еще немного подзаработать, предложив сайтлок некоторым порталам. Что такое сайтлок? Это специальная версия игры, которая делается для конкретного портала, туда вставляется брендинг, а также дополнительная проверка, которая должна запрещать запуск игры вне сайта портала. То есть такая версия игры будет лежать только на сайте компании, которая покупает сайтлок. Так как игра будет запускаться только у них, а весь мир будет играть в версию, которую купил праймари-спонсор, то за сайтлок обычно платят не очень много, хотя бывали случаи продажи и за 800-1200$, что очень круто! Итак, вставить новый брендинг для сайтлок-спонсора не проблема, покидали его картинки, привязали слушатель на мышку, поэтому обычно возникают вопросы только насчет проверки на запуск игры вне сайта сайтлок-портала...
Лично я особо не заморачиваюсь, а оставляю элементарную проверку, которая добавляется в прелоадер игры, что-то вроде этого:

if ((stage.loaderInfo.url.indexOf('fgl.com') > 0) ||
     (stage.loaderInfo.url.indexOf('flashgamelicense.com') > 0))
{
                var mainClass:Class = getDefinitionByName("Main") as Class;
                addChild(new mainClass(this) as DisplayObject);
}

Вот так все просто... Если проверка не пройдена, то мы можем не показать кнопку Play, или же не делать addChild() для меню... Это уже все зависит от вашей фантазии или от заказа спонсора. Думаете, такую проверку можно легко сломать? Конечно можно! Но кому это нужно? Я, признаться, даже и не знаю... базовый функционал реализован, и отлично! Если надо - взломают любую флешку (формат открытый, декомпилером много, так что со взломом обычно много проблем не возникает)
В примере видно, что я в обязательном порядке лочу игру при выкладывании на аукцион. Иначе недобрые саморетяне могут украсть игру раньше продажи. Также отмечу, что я оборачиваю игру с помощью secureSWF (их сайт), говорят, защита у них что надо...


Загадочный DeltaTime
Еще давным-давно, программируя на Delphi, я добавлял или перекрывал почти всем объектам метод Update(), который вызывался при каждом кадре или тике таймера. В этом методе обновлялись картинки кнопок, проигрывалась анимация, да и вообще выполнялось все-превсе!
Перейдя на флеш, мне сразу захотелось пробрасывать такой Update() везде, ну а как по-другому?! Оказалось, что во флеше есть замечательное событие ENTER_FRAME, которым по сути можно заменить метод Update у объектов. Кто желает обновляться, добавляет слушателя на событие ENTER_FRAME, и там уже выполняет все необходимые действия. Как оказалось, этот метод удручает скоростью выполнения. Дело в том, что обновления происходят почти в каждом объекте: лифт движется, дверь открывается, прыгучка проигрывает анимацию, веревка раскачивается, персонаж так вообще перемещается по уровню, а камера должна за ним следить, звуки проигрываются по тригерам, да много чего в игре должно обновляться почти каждый кадр.  Так вот, если всем объектам добавлять слушателей ENTER_FRAME, то производительность сильно упадет, чего никак нельзя допустить. Именно поэтому я вернулся к своей идее с методом Update() в каждом важном классе. Как это работает, да очень просто:

public function Update(aDeltaTime: Number): void
{
  // выполняем необходимые действия
  fDoors.Update(aDeltaTime);
  fJumpers.Update(aDeltaTime);
  fPlayer.Update(aDeltaTime);
  fSounds.Update(aDeltaTime);
 //
}

Здесь стоит заметить, что появился какой-то странный DeltaTime. Что это? На самом деле это довольно замечательная и красивая уловка, которая сильно облегчает жизнь. Чтобы не ломать голову над количеством кадров, фпс и так далее мы передаем интервал времени в секундах, который прошел с последнего вызова Update. В нашем случае это будет 1 / frameRate. Если в какой-то момент мы захотим поменять frameRate игры, то скорость всех программных действий останется прежней, могут возникнут проблемы только с покадровой анимацией. На сколько пикселей передвинется игрок? На величину:

x += fVelocityX * aDeltaTime;
y += fVelocityY * aDeltaTime;

Как обновить физику? Тоже легко:

fBox2dPhysicWorld.Step(aDeltaTime);

С загадочным aDeltaTime разобрались.
Остался последний вопрос: откуда взять самый верхний Update? Кто вызовет его? Ну, здесь как раз нам и поможет ENTER_FRAME, но в итоге он будет только один, порождающий целую лавину внутренних Update:
fDeltaTime = 1 / stage.frameRate; // интервал в секундах
addEventListener(Events.ENTER_FRAME, onEnterFrame);

protected function onEnterFrame(e: Event): void
{
  fGameManager.Update(fDeltaTime);
}

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

Сообщения, схожие по тематике:

11 коммент.:

  1. Иногда время вызова между кадрами может отличатся от 1/frameRate. Причем как в большую сторону, так и в меньшую. И для определения deltaTime я использую функцию getTimer:

    private function update(e:Event):void
    {
    var time:Number = getTimer();
    var deltaTime:Number = (time - this._prevTime) / 1000;
    this._prevTime = time;

    this.states.update(deltaTime);
    }

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

      Удалить
    2. Странно, использую твинер от GreenSock и не замечал таких проблем=) Но то, о чем ты говоришь, конечно же имеет место быть =) Я просто стараюсь во время всяких твин-анимаций уменьшать нагрузку на процессор.

      Удалить
    3. я юзаю gTweener (как-то исторически сложилось). но дело думаю не в инструменте, а в моих руках... пока не получается настолько вникнуть во флеш, чтобы правильно оптимизировать те или иные моменты...
      огромная благодарность за твои дополнения! всегда приятно читать меткие замечания!

      Удалить
  2. Что-то я не понял.

    Допустим у нас DeltaTime = 1 / 60.
    Скорость 10.
    X = 0.

    Т.е. получается:

    X:= Trunc(X + 10 * DeltaTime);

    Получаем 0.

    Почему?

    ОтветитьУдалить
    Ответы
    1. Зачем тебе trunc()? конечно с отсечением дробной части будет всегда 0. trunc() убери и всё норм. Ну а x само собой должна быть single или double.

      Удалить
    2. да, как верно подметил Monax, все дело в Trunc...
      за один кадр твой объект может переместиться меньше, чем на пиксель... поэтому его позицию надо хранить как можно точнее, без округлений.
      на твоем примере все прозрачно. за один кадр тело переместится на 0.17, а ты округляешь к нулю, зачем?
      это также, как если бы ты хранил позицию в километрах, тогда за секунду тело проходило бы всего несколько сотых километра... где тело сейчас? допустим, в координате 0.1 км... а через секунду? где-то около 0.12 км. округлять такое нельзя!

      Удалить
    3. Допустим Х: single.
      Тогда получается:

      X:= X + 10 * 1 / 60;
      0:= 0 + 10 * 0.02;

      Двигается как черепаха.

      А если убрать 1 / 60 (Домножение на Delta Time), то, естесственно, все "летает".

      Или я чего-то не понял?

      P.S. Х округляется только при выводе.
      Пример рисую через Canvas.Draw(), где нельзя использовать вещественные числа.



      Удалить
    4. на всякий случай поясню, что у меня за переменные в этой формуле:
      x += fVelocityX * aDeltaTime;
      x - физическая координата объекта
      fVelocityX - его скорость по оси X
      aDeltaTime - время в секундах

      формулы в физике всегда имеют размерность... поэтому я немного расскажу про это... итак, ты написал "допустим скорость 10", если ты по координате x рисуешь объект, тогда немного дополню твою фразу словами "допустим скорость 10 пикселей в секунду". вот теперь, надеюсь, многое должно стать ясным.
      скорость - размерная величина и нужно понимать, какие значения она должна принимать. в твоем примере ты наверно хочешь видеть быстрое движение на экране, что будет соответствовать скорости 300-500 пикселей в секунду! то есть тебе нужно не DeltaTime убирать, а корректировать скорость, подбирать комфортные для тебя значения...

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

      Удалить
  3. У меня (В Delphi) объект позицию вычисляет в целых числах. (Для того же Canvas.Draw());

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

      Удалить