Статьи

Вокруг поворота с текстом на пути во Flash

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

Мы увидим, как разбить TextField и разместить все символы вдоль такой кривой, сохранив форматирование текста и расстояние между символами. Наконец, мы посмотрим, как рисовать текст по спирали.


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

Измените текст в текстовом поле и поиграйтесь с параметрами.

Представьте себе этот вымышленный сценарий: утром в понедельник вы пьете свой кофе, а затем ваш графический дизайнер приходит и просыпается и спрашивает вас: «Эй, я создаю анимацию загрузки игры, и я хотел бы, чтобы текст процесса загрузки соответствовал этому» круг, я думаю, у вас есть что-то, что я могу скопировать и вставить, чтобы сделать работу?

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

Шрифт, используемый в демоверсии, — это бесплатный шрифт Elegan Tech, который вы можете бесплатно загрузить и использовать с dafont.com

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


Начните с создания нового файла CircularTextTest.fla в Flash Professional, затем откройте окно «Компоненты» и перетащите ползунок, метку и флажок в свою библиотеку Flash.

В файле Flash создайте большое текстовое поле, затем установите его в качестве входного TextField и tfTemplate ему имя: tfTemplate . Для этой демонстрации мы решили использовать шрифт EleganTech , поэтому установите для него значение шрифта TextField, затем импортируйте шрифт и убедитесь, что диапазон необходимых символов импортирован.

Перетащите флажок из библиотеки на сцену, затем скопируйте и вставьте его 3 раза. Каждый флажок будет использоваться для установки демонстрационного параметра: показать / скрыть привязки, перевернуть текст снаружи / внутри, обрезать (или нет) пустое пространство.

Метка и выбранные значения зависят от вас, чтобы установить начальные значения демо. Важно установить имена экземпляров флажков, чтобы можно было их использовать. Первый будет chkShowAnchors , второй — chkTxtOutside а третий — chkTrimWhite .

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

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

Теперь вы можете создать второй слайдер и связанную с ним метку, соответственно с именами sliderCharOffset и labelOffset . Для демонстрации я использовал значение по умолчанию 7, минимальное и максимальное значения от -50 до 50, но это на ваше усмотрение, вы можете установить его в соответствии со своими потребностями.
Наконец, вы можете установить класс документа в CircularTextTest , который будет содержать весь демонстрационный код. Класс еще не создан, это следующий шаг, сейчас вам просто нужно установить имя класса в файле Flash, как описано в кратком совете Как использовать класс документа во Flash .

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


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

Давайте создадим файл класса CircularTextTest.as вместе с вашим файлом Flash. Самые первые шаги будут состоять в том, чтобы импортировать нужные нам классы, объявить активы, которые мы поместили на сцену, и подготовить подпрограмму, которую мы вызовем для выполнения работы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package {
     
    import fl.controls.CheckBox;
    import fl.controls.Label;
    import fl.controls.Slider;
    import fl.events.SliderEvent;
     
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.events.TextEvent;
     
         
        public class CircularTextTest extends MovieClip
        {
            /* —— Assets defined in the Flash timeline for the demo —— */
            /** The editable TextField */
            public var tfTemplate:TextField;
            /** Checkbox: show character anchors */
            public var chkShowAnchors:CheckBox;
            /** Checkbox: text orientation */
            public var chkTxtOutside:CheckBox;
            /** Checkbox: trimWhite/condenseWhiteSpace */
            public var chkTrimWhite:CheckBox;
            /** Slider : the value of the circle radius */
            public var sliderRadius:Slider;
            /** The label to display the current radius value */
            public var labelRadius:Label;
            /** Slider : the offset of the characters from the circle */
            public var sliderCharOffset:Slider;
            /** The label to display the current offset value */
            public var labelOffset:Label;
             
             
             
            public function CircularTextTest() {
                stop();
                addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            }
             
            protected function onAddedToStage(evt:Event):void
            {
                createCircularText();
            }
             
            protected function createCircularText():void
            {
                // Here will comes all the interesting code
            }
                 
             
             
        }
         
}

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


Нам нужно убедиться, что мы знаем, когда изменяется содержимое TextField, поэтому мы initSwf обработчик инициализации initSwf чтобы добавить прослушиватели событий в события change и textInput . Эти события будут вызывать свой собственный обратный вызов, который, в свою очередь, вызовет процедуру createCircularText() . Этот дополнительный шаг существует, если вы хотите выполнить некоторые проверки в зависимости от типа события; чтобы привести пример, вы можете захотеть добавить некоторый код, чтобы убедиться, что текст корректен , или выполнить некоторые специальные действия, когда текст пустой, перед вызовом createCircularText() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
protected function onAddedToStage(evt:Event):void
{
    tfTemplate.addEventListener(TextEvent.TEXT_INPUT, onTextInputHandler, false,0,true);
    tfTemplate.addEventListener(Event.CHANGE, onTextChangeHandler, false,0,true);
     
    createCircularText();
}
 
protected function onTextInputHandler(evt:TextEvent):void
{
    createCircularText();
}
protected function onTextChangeHandler(evt:Event):void
{
    createCircularText();
}

Флажок уже установлен и готов во Flash, поэтому нам просто нужно прослушать обновления для флажка chkTxtOutside . Точно так же, как для поля ввода, мы обновляем нашу процедуру инициализации, чтобы добавить прослушиватель событий для события change из флажка. На этот раз мы будем использовать только один универсальный onCheckBoxChange вызов события onCheckBoxChange для всех onCheckBoxChange , который мы создадим прямо сейчас.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
protected function onAddedToStage(evt:Event):void
{
    // …
    // Previous code still here
    // …
     
    chkTxtOutside.addEventListener(Event.CHANGE, onCheckBoxChange, false, 0, true);
    //stage.addEventListener(MouseEvent.CLICK, onCheckBoxChange);
     
    createCircularText();
}
 
protected function onCheckBoxChange(event:Event):void
{
    createCircularText();
}

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


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

01
02
03
04
05
06
07
08
09
10
protected function onAddedToStage(evt:Event):void
{
    // …
    // Previous code still here
    // …
     
    chkShowAnchors.addEventListener(Event.CHANGE, onCheckBoxChange, false, 0, true);
     
    createCircularText();
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
protected function onAddedToStage(evt:Event):void
{
    // …
    // Previous code still here
    // …
     
    sliderRadius.addEventListener(SliderEvent.CHANGE, onRadiusChange, false,0,true);
     
    createCircularText();
}
 
protected function onRadiusChange(evt:SliderEvent):void
{
    labelRadius.text = «Radius : » + sliderRadius.value;
    createCircularText();
}

Очевидно, это почти та же история с радиусом, но с другой меткой и другим ползунком.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
protected function onAddedToStage(evt:Event):void
{
    // …
    // Previous code still here
    // …
     
    sliderCharOffset.addEventListener(SliderEvent.CHANGE, onCharOffsetChange, false,0,true);
     
    createCircularText();
}
 
protected function onCharOffsetChange(evt:SliderEvent):void
{
    labelOffset.text = «Character offset from the circle : » + sliderCharOffset.value;
    createCircularText();
}

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

Чтобы преобразовать текст, мы будем вызывать CircularText.generateText(templateTextField, targetHostClip, options) со всеми параметрами, хранящимися в экземпляре CircularTextOptions . Внутренняя работа этих классов будет зависеть от следующих шагов, сейчас мы должны прочитать значения из наших компонентов и отправить их этим объектам, которые будут выполнять эту работу за нас. Эти классы будут храниться в пакете utils.clips , поэтому мы должны убедиться, что добавили это содержимое в операторы импорта.

Нам понадобится клип, в который нужно поместить кружок отдельного персонажа MovieClips; это будет называться animHost .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package {
     
    // ….
    // Previous import still here
    import utils.clips.*;
     
    public class CircularTextTest extends MovieClip
    {
        /** The movieclip that will ‘host’ all the created character clips */
        public var animHost:MovieClip;
             
        protected function createCircularText():void
        {
            // For the purpose of the demo we create a movieclip at the center of the stage
            if (animHost == null)
            {
                animHost = new MovieClip();
                animHost.x = 275;
                animHost.y = 200;
                addChild(animHost);
            } else {
                // We make sure the previous character clips are removed
                for (var i:int = animHost.numChildren; i>0; i—) {
                    animHost.removeChildAt(0);
                }
            }
             
            var options:CircularTextOptions = new CircularTextOptions();
            if (chkTxtOutside.selected) {
                options.characterOrientation = CircularTextOptions.CHARACTER_ORIENTATION_OUTSIDE;
            } else {
                options.characterOrientation = CircularTextOptions.CHARACTER_ORIENTATION_INSIDE;
            }
            options.radius = sliderRadius.value;
            options.radiusOffset = sliderCharOffset.value;
            options.trimWhite = chkTrimWhite.selected;
            options.showAnchors = chkShowAnchors.selected;
            CircularText.generateText(tfTemplate, animHost, options);
             
        }

Чтобы завершить демонстрацию, давайте повернем круг! Для этого нам просто нужно создать объект Timer , сделать его галочкой 25 раз в секунду и каждый раз вызывать обработчик обновлений. Функция обновления просто немного увеличивает свойство rotation MovieClip узла круга. Вот и все — вы сделали это повернуть.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package {
     
    // ….
    // Previous import still here
    import flash.events.TimerEvent;
    import flash.utils.Timer;
     
        public class CircularTextTest extends MovieClip
        {
            /** A Timer object to call an update callback and spin the circle */
            protected var rotationTimer:Timer;
             
            protected function createCircularText():void
            {
                // …
                // Previous code still here
                 
                // We create a timer to turn the movieClip
                if (rotationTimer == null)
                {
                    rotationTimer = new Timer(1/25, 0);
                    rotationTimer.addEventListener(TimerEvent.TIMER , onTimer, false,0,true);
                    rotationTimer.start();
                }
            }
             
            protected function onTimer(event:TimerEvent):void
            {
                animHost.rotation += 0.1;
            }

Теперь мы преобразуем линейную позицию в угол. Мы будем знать положение текста в линейной x, и нам нужно преобразовать его в положение на окружности. Чтобы преобразовать точку на отрезке в точку на окружности, нам нужно несколько вещей:

  • Размер сегмента, который будет шириной шаблона TextField . Давайте назовем это xMax .
  • Положение х на сегменте, которое будет позицией символа в шаблоне.
  • Радиус круга, который мы будем устанавливать.
  • Альфа- угол, чтобы построить точку на окружности. Этот угол будет вычислен из x и размера сегмента. Мы рассмотрим добавление фазы , которая является смещением.

Для простоты: когда x будет 0, alpha также будет 0. С другой стороны, когда x равен xMax, тогда alpha = 360 ° (или в радианах : 2 * PI).

Для всех промежуточных положений альфа = 2 * PI * x / xMax (в радианах).

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

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

Наконец, чтобы получить положение персонажа на круге из его центра, это очень простая тригонометрия:

1
2
clip.x = radius * Math.cos(angleInRadians);
clip.y = radius * Math.sin(angleInRadians);

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

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


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

ActionScript предоставляет нам sourceTf.getBounds (sourceTf.parent); метод для получения точных размеров TextField (этот метод доступен для любого DisplayObject). Метод getBounds требует целевого координатного пространства. Наиболее распространенным вариантом использования нашего преобразования будет создание круга на том же уровне, что и шаблон TextField, поэтому мы будем использовать родительское координатное пространство шаблона TextField, чтобы позаботиться о любых преобразованиях более высокого уровня.

Использование позиции символа в исходной строке слишком ограничено, поэтому мы не будем использовать такую ​​основную меру. Мы будем вызывать sourceTextField.getCharBoundaries(nthCharacter) чтобы получить позицию и размер любого символа. Это вернет прямоугольник, поэтому мы будем знать ширину символа и его точное положение.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
var i:int;
var charCnt:int = sourceTf.length;
var charBounds:Rectangle;
         
// the default ‘source’ bounds are the whold textFields bounds
var templateBounds:Rectangle = sourceTf.getBounds(sourceTf.parent);
             
// If we trim the ‘extra’ white, then the boundaries of the last visible character
if (options.trimWhite)
{
    charBounds = sourceTf.getCharBoundaries(0);
    templateBounds.left = charBounds.left;
    charBounds = null;
    i = 1;
    do {
        charBounds = sourceTf.getCharBoundaries(sourceTf.length-i);
        i++;
    } while (charBounds == null);
    templateBounds.right = charBounds.right;
}

После этого блока кода переменная templateBounds будет иметь желаемую ширину (как мы увидим позже, option.trimWhite является логическим значением, указывающим, игнорируем ли мы лишние пробелы в TextField).

Теперь у нас есть правильное значение xMax , и я предполагаю, что вы поняли, что мы будем использовать тот же метод getCharBoundaries чтобы получить точную позицию x каждого символа в исходном TextField, давая нам значения x для использования в наших формулах выше.


Прежде чем идти дальше в кодировании, мы формализуем параметры, которые будем обрабатывать, и поместим их все в класс параметров. Мы будем использовать этот «контейнер опций» на следующих шагах, чтобы легко охватить различные варианты циклического преобразования. Этот класс параметров может быть простым универсальным объектом, но использование предопределенных констант и значений упрощает использование класса CircularText, позволяет строгое типирование — следовательно, облегчает поиск ошибок во время компиляции — и позволяет нам устанавливать значения по умолчанию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package utils.clips
{
    public class CircularTextOptions
    {
         
        static public const CHARACTER_ORIENTATION_INSIDE:Number = 90;
        static public const CHARACTER_ORIENTATION_OUTSIDE:Number = -90;
         
        /** Radius of the circle */
        public var radius:Number = 100;
        /** Angular offset to apply (to make the text spin around the circle */
        public var phase:Number = 0;
         
         
        /** Character orientation, can be
         * CircularTextOptions.CHARACTER_ORIENTATION_INSIDE or
         * CircularTextOptions.CHARACTER_ORIENTATION_OUTSIDE */
        public var characterOrientation:Number = CHARACTER_ORIENTATION_INSIDE;
         
        /** Useful for debugging, this option enables the display of the
         * character anchor points (might help to fine-tune offests, …) */
        public var showAnchors:Boolean = false;
         
        /** Offset in pixel from the circle to the character.
         * the radius and this offset, you can control the character spacing.
        public var radiusOffset:Number = 0;
         
        /** This option sets whether the circle is based on the with of the source
         * textField or the width of its contents.
         * are still computed;
        public var trimWhite:Boolean = true;
         
        public function CircularTextOptions()
        {
        }
    }
}

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

Нам понадобится несколько локальных переменных для хранения промежуточных значений:

1
2
3
4
var degrees:Number;
var radians:Number;
var charContainer:MovieClip;
var radius:Number = options.radius;

Тогда мы начнем цикл по всем исходным персонажам

1
2
3
4
5
6
7
8
for (i = 0; i < charCnt; i++)
{
    charBounds = sourceTf.getCharBoundaries(i);
    if (charBounds != null) // special chars, like carriage return, do not have bounds, hence this test
    {
        //…
    }
}

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


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

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

Если это все еще не ясно, просто измените минус в options.phase - (charBounds.right - 0.5*charBounds.width) * 360/templateBounds.width на плюс и опубликуйте его, чтобы сразу увидеть, как он будет выглядеть, если вы этого не сделаете позаботьтесь о ориентации персонажа. Наконец, мы конвертируем градусы в радианы , так как sin () и cos () нуждаются в углах в радианах для своих аргументов.

1
2
3
4
5
6
7
8
// Compute the angular offset of the char based on its metrics
if (options.characterOrientation == CircularTextOptions.CHARACTER_ORIENTATION_INSIDE)
{
    degrees = options.phase + (charBounds.left + 0.5*charBounds.width) * 360/templateBounds.width;
} else {
    degrees = options.phase — (charBounds.right — 0.5*charBounds.width) * 360/templateBounds.width;
}
radians = degrees*(Math.PI/180);

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

01
02
03
04
05
06
07
08
09
10
11
12
charContainer = new MovieClip();
if (options.characterOrientation == CircularTextOptions.CHARACTER_ORIENTATION_INSIDE)
{
    charContainer.rotation = Math.round(degrees)+ options.characterOrientation;
    charContainer.x = radius * Math.cos(radians);
    charContainer.y = radius * Math.sin(radians);
} else {
    charContainer.rotation = Math.round(degrees)+ options.characterOrientation;
    charContainer.x = radius * Math.cos(radians);
    charContainer.y = radius * Math.sin(radians);
}
host.addChild(charContainer);

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

1
2
3
4
public class CircularTextOptions
    {
        static public const CHARACTER_ORIENTATION_INSIDE:Number = 90;
        static public const CHARACTER_ORIENTATION_OUTSIDE:Number = -90;

Теперь, когда мы создали хост MovieClip для этого персонажа, разместили его вдоль круга и повернули в правильном направлении, мы должны поместить в него что-то, например, TextField с символом, иначе ничего не будет видно 😉

Это довольно просто: создайте новый TextField и установите все его атрибуты. Наиболее важным является использование embedFonts , так что текст все еще будет виден после поворота.

1
2
3
4
5
6
7
8
// Set Text and TextFormat
newTf = new TextField();
newTf.type = TextFieldType.DYNAMIC;
newTf.selectable = false;
newTf.embedFonts = true;
newTf.autoSize = TextFieldAutoSize.CENTER;
newTf.antiAliasType = AntiAliasType.NORMAL;
newTf.text = sourceTf.text.charAt(i);

У нас есть TextField с текстом в нем, теперь мы должны отформатировать этот текст, чтобы он выглядел как оригинал. Мы будем использовать try / catch для деформации setTextFormat потому что если что-то пойдет не так при форматировании текста из-за отсутствия шрифтов, здесь появится ошибка, и это поможет вам отладить проблемы со шрифтами.

1
2
3
4
5
6
charTf = sourceTf.getTextFormat(i);
try {
    newTf.setTextFormat(charTf);
} catch (e:Error) {
    trace(‘textFormat error’);// place a breakpoint here to watch for potential font issues…
}

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

1
2
newTf.x = -(charBounds.width)/2;
newTf.y = -(charBounds.height) + options.radiusOffset;

Вот и все, мы сделали. Мы прошли все этапы и теперь у нас полностью рабочий режим.


И вот идет полный класс CircularText мы создали на всех этапах этого урока:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package utils.clips
{
    import fl.text.TLFTextField;
     
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFieldType;
    import flash.text.TextFormat;
    import flash.text.AntiAliasType;
    import flash.text.Font;
     
    /**
     * Use CircularText.generateText(templateTextfield, targetMovieClip,
     * options) to create a well-spaced character distribution around a circle
     * in the target movieclip.
     */
    public class CircularText
    {
          
     
        public function CircularText()
        {
        }
         
         
        /**
         * @param sourceTf The Textfield wich is used as source and template.
         * Typically a dynamix textField, invivisible, in which
         * the text and textFormat are set by the rest of the
         * program.
         * @param host The target movieClip in which the text circle will
         * be created.
         * @param options A CircularTextOptions object with formatting option
         * to make the text circle looks like we want to.
         */
        static public function generateText(sourceTf:TextField,
                                            host:MovieClip,
                                            options:CircularTextOptions=null):void
        {
            if (options == null) options = new CircularTextOptions();
             
            // We explode the original textField into smaller clips, each one containing a character
             
            var newTf:TextField;
            var charTf:TextFormat;
            var i:int;
            var charCnt:int = sourceTf.length;
            var charBounds:Rectangle;
             
            // the default ‘source’ bounds are the whold textFields bounds
            var templateBounds:Rectangle = sourceTf.getBounds(sourceTf.parent);
             
            // If we trim the ‘extra’ white, then the boundaries of the last visible character
            if (options.trimWhite)
            {
                charBounds = sourceTf.getCharBoundaries(0);
                templateBounds.left = charBounds.left;
                charBounds = null;
                i = 1;
                do {
                    charBounds = sourceTf.getCharBoundaries(sourceTf.length-i);
                    i++;
                } while (charBounds == null);
      
                templateBounds.right = charBounds.right;
            }
             
            var degrees:Number;
            var radians:Number;
            var charContainer:MovieClip;
            var radius:Number = options.radius;
            for (i = 0; i < charCnt; i++)
            {
                charBounds = sourceTf.getCharBoundaries(i);
                if (charBounds != null) // special chars, like carriage return, do not have bounds, hence this test
                {
                    // Compute the angular offset of the char based on its metrics
                    if (options.characterOrientation == CircularTextOptions.CHARACTER_ORIENTATION_INSIDE)
                    {
                        degrees = options.phase + (charBounds.left + 0.5*charBounds.width) * 360/templateBounds.width;
                    }else {
                        degrees = options.phase — (charBounds.right — 0.5*charBounds.width) * 360/templateBounds.width;
                    }
                    radians = degrees*(Math.PI/180);
                     
                    // Create a new Textfield to render this character
                    newTf = new TextField();
                    charTf = sourceTf.getTextFormat(i);
                     
                    // Set Text and TextFormat
                    newTf.type = TextFieldType.DYNAMIC;
                    newTf.selectable = false;
                    newTf.embedFonts = true;
                    newTf.autoSize = TextFieldAutoSize.CENTER;
                    newTf.antiAliasType = AntiAliasType.NORMAL;
                      
                    newTf.text = sourceTf.text.charAt(i);
                    try {
                        newTf.setTextFormat(charTf);
                    } catch (e:Error) {
                        trace(‘textFormat error’);// place a breakpoint here to watch for potential font issues…
                    }
                     
                    newTf.x = -(charBounds.width)/2;
                    newTf.y = -(charBounds.height) + options.radiusOffset;
                     
                    charContainer = new MovieClip();
                    if (options.showAnchors)
                    {
                        // here comes the graphic aspect of the anchors, if you want something else
                        charContainer.graphics.beginFill(0xff00ff, 1);
                        charContainer.graphics.drawRect(0,0,5,5);
                        charContainer.graphics.endFill();
                    }
                    charContainer.addChild(newTf);
                     
                    if (options.characterOrientation == CircularTextOptions.CHARACTER_ORIENTATION_INSIDE)
                    {
                        charContainer.rotation = Math.round(degrees)+ options.characterOrientation;
                        charContainer.x = radius * Math.cos(radians);
                        charContainer.y = radius * Math.sin(radians);
                    } else {
                        charContainer.rotation = Math.round(degrees)+ options.characterOrientation;
                        charContainer.x = radius * Math.cos(radians);
                        charContainer.y = radius * Math.sin(radians);
                    }
                     
                    host.addChild(charContainer);
                }
            }
             
        }
    }
}

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

1
2
3
4
5
6
7
8
9
// tfTemplate is the TextField containing the source text to transform
// animHost is a movieClip in which we will create the circle
var options:CircularTextOptions = new CircularTextOptions();
options.characterOrientation = CircularTextOptions.CHARACTER_ORIENTATION_INSIDE;
options.radius = 50;
options.radiusOffset = 0;
options.trimWhite = true;
options.showAnchors = false;
CircularText.generateText(tfTemplate, animHost, options);

Этот код выглядит как то, что вы уже видели? Что ж, так и должно быть, потому что этот пример использования действительно близок к методу createCircularText который мы создали в начале. Теперь вы можете видеть, что почти все на месте.


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

Для тех, кто перепрыгнул через первый раздел, код в демке очень прост: простые слушатели обновляют циклический текст, когда что-то меняется. Он имеет addedToStage прослушиватель addedToStage для настройки других прослушивателей на компонентах ввода (TextField, некоторые ползунки и некоторые флажки); несколько функций слушателя, которые все вызывают createCircularText() ; и эта createCircularText() которая устанавливает объект параметров со значениями, считанными из компонентов, и вызывает метод CircularText.generateText точно так же, как в наших фрагментах выше.


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

01
02
03
04
05
06
07
08
09
10
11
public class SampleSmallCircularTextOptions extends CircularTextOptions
{
         
    public function SampleSmallCircularTextOptions()
    {
        super();
        radius = 50;
        characterOrientation = CHARACTER_ORIENTATION_INSIDE;
        // showAnchors = AppManager.isDebug;
    }
}

В этом классе для создания кругового текста требуется только исходный TextField и целевой MovieClip для размещения круга, что дает нам действительно небольшой объем кода:

1
2
3
// tfTemplate is the TextField containing the source text to transform
// animHost is a movieClip in which we will create the circle
CircularText.generateText(tfTemplate, animHost, new SampleSmallCircularTextOptions());

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

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

  1. Радиус от центра к персонажу не постоянен: при каждом шаге по спирали радиус увеличивается на небольшую величину
  2. Длина пути может быть больше, чем просто 2*PI*Radius , так что символы идут «ниже» предыдущих.

Здесь представлены обновления переменных CircleTextOptions чтобы иметь возможность хранить эти два параметра:

1
2
3
4
5
6
/* —- Spiral specific options —- */
/** By default, only 1 turn, therefore a circle */
public var turnCount:Number = 1;
/** Damping make the ‘circle’ radius smaller (or bigger) each time;
    if it’s equal to one, then the radius doesn’t change */
public var spiralDamping:Number = 1;

Затем нам нужно обновить наш метод CircularText.generateText() чтобы позаботиться об этих параметрах. Как уже упоминалось, радиус больше не будет постоянным.

1
2
3
4
5
var radius:Number = options.radius;
for (i = 0; i < charCnt; i++)
{
    // Spiral update : after each character the spiral damping is applied
    radius *= options.spiralDamping;

Таким образом, начальное значение радиуса будет таким же, как наш круг. Тогда для каждого символа он будет умножен. Значение по умолчанию 1 сохранит радиус без изменений, но если мы установим немного меньшее значение, например 0,97, радиус будет меньше с каждым шагом.

Затем мы хотим контролировать длину спирали. Сразу после вычисления значения degrees для текущего символа, мы хотим умножить его на число витков спирали. Если спираль зацикливается только один раз, она будет похожа на круг (если радиус оставить без изменений). Если спираль повторяется более одного раза, символы будут перекрываться. Таким образом, значение в degrees должно быть умножено на turnCount чтобы преобразовать наше значение за 1 оборот в его новое значение за n витков.

1
degrees = options.turnCount * degrees;

Вот и все, больше ничего не нужно делать, чтобы иметь настраиваемую спираль! Вы можете отредактировать метод createCircularText() тестового класса CircularTextTest, чтобы попробовать его.

1
2
3
4
5
6
// Previous option settings still there …
// Spiral update : We will use a damping != 1 and a number of turn != 1
options.turnCount = 1.5;
options.spiralDamping = 0.97;
 
CircularText.generateText(tfTemplate, animHost, options);

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


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