My:Abs
Машина состояний (state machine) является универсальным объектом для программирования в редакторе сцен сложного поведения объектов. Для машин с типовым набором состояний и поведений, в дальнейшем будут вводиться отдельные объекты.
Логика поведения машины состояний разбивается на отдельные узлы (состояния). Объект всегда находится строго в одном состоянии. Переход из одного состояния в другое происходит либо в результате внешнего воздействия на объект, либо в результате окончания некоторых процессов протекающих внутри объекта.
Параметры состояний задаются в редакторе сцены. Ниже, вместо скриншотов панели свойств, приводятся примеры на языке xml (так как это выглядит во внутреннем файле сцены).
Содержание
Введение
Внутри каждого состояния (далее тег st) могут находиться 4 вида тегов:
- Инициализация.
- init - инициализация состояния
- set - установка состояния другого объекта
- Процессы:
- time - временные паузы
- move - как двигаться
- alpha - как менять прозрачность
- scale - как изменять размеры
- rot - как вращаться
- spin - как двигаться по окружности
- phys - движение в силовом поле.
- Воздействия:
- clik - что делать при клике на объект
- Проверки:
- wait - жать, пока другие объекты не окажутся в неком состоянии
Рассмотрим пример. Пусть объект, находится в состоянии state="stop". При клике на него мышкой, он переходит в состояние st="run", в котором находится 3000 ms, через которые возвращается в состояние "stop". В xml-файле сцены он описывается следующим образом:
<obj id="1" kn="sts" w="64" h="64" x="734" y="534" state="wait"> <st id="stop"> <!-- ждем клика --> <init res="101" /> <!-- рисовать граф.ресурс 101 --> <click go="run" /> <!-- при клике - идем в состояние run--> </st> <st id="run"> <!-- реакция на клик - другая картинка--> <init res="102" /> <!-- рисовать граф.ресурс номер 102 --> <time tm="3000" go="wait" /> <!-- через время tm переходим в wait --> </st> </obj>
Наличие поля go="состояние" в любой команде инициирует остановку всех процессов и смену состояния.
Воздействие на объект и условия
Если в команде click указано значение go, то происходит переход в новое состояние
<click go="run" />
Поля go может и не быть. Тогда все процессы внутри состояния (например, перемещения) останавливаются до следующего клика, которым они запускаются снова:
<click/>
При появлении поля go в любой команде (не только в click) происходит анализ секции проверки состояний других объектов. Переход осуществляется, только если эта секция выполняется. Например, пусть смена состояния происходит только, если объект номер 2 находится в состоянии "show": Если нужно отслеживать состояние нескольких объектов, то делаем несколько секций click:
<click go="run"/> <wait obj="2" st="show"/>
Команд wait может быть несколько. В этом случае работает логическое или (или 2-й объект в st=show или 3-й в st=play):
<click go="run"/> <wait obj="2" st="show"/> <wait obj="3" st="play"/>
В случае логического и формат следующий:
<wait> <and obj="2" st="show"/> <and obj="3" st="run"/> </wait> <wait obj="4" st="play" />
Т.е. (2-й в show и 3-й в run) или (4-й в play).
Повтор команд
Все теги могут встречаться при описании данного состояния по несколько раз. Интерпретация таких повторов зависит от типа команды:
- В последовательности одинаковых команд инициализации (init,init,...) выполняется только первая. Выполнение следующих команд инициируется другими командами.
- Команды процессов выполняются параллельно и независимо. Например, последовательность move, move, move, alpha, alpha сразу выглядит в виде параллельных цепочек, выполняющихся одновременно сверху вниз. При начале функционирования состояния запускается первый move и первая alpha. Когда одна из них выполнится происходит переход на следующую команду с таким же именем:
move alpha move alpha move
- Условная проверка (wait) производится на выходе из состояния. Выход происходит, если условие выполнилось.
- Каждая команда может управлять номером выполнения команды из списка команд с данным именнем
Рассмотрим перемещение персонажа по экрану. Пусть, различные этапы движения персонажа собраны в графическом ресурсе в прямоугольную таблицу (по строкам расположено движение вперед, поворот и т.п.). Тогда можно указать начальный с1, конечный c2 фреймы в строке таблицы r (по умолчанию r=0). Движение вправо и вверх можно реализовать двумя способами. Либо последовательность двух состояний (left и up):
<st id="left"> <init res="101" c1="0" c2="15" r="0" /> <!-- 16 кадров ходьбы --> <move dx="100" tm="500" go="up"/> <!-- сдвигаемся на dx за dt и переходим в go --> </st> <st id="top"> <init res="101" c1="0" c2="15" r="1" /> <!-- 16 кадров подъема вверх --> <move dy="100" tm="500"/> <!-- сдвигаемся на dy за dt и останавливаемся --> </st>
Этот же процесс движения можно реализовать как одно состояние:
<st id="move"> <init res="101" c1="0" c2="15" r="0" /> <!-- 16 кадров нулевой строки --> <move dx="100" tm="500" init="1"/> <!-- сдвигаемся на dx за dt и переходим в go --> <init res="101" c1="0" c2="15" r="0" /> <!-- 16 кадров нулевой строки --> <move dy="100" tm="500"/> <!-- сдвигаемся на dy за dt и останавливаемся --> </st>
При начале функционирования этого состояния выполняется инициализация с заданием графики. Затем происходит горизонтальное перемещение. Когда оно заканчивается выполняется следующая команда init (см. свойство init="1" в первом move и помним, что все команды нумеруются с 0). После этого сразу запускается следующий move.
Инициализация
При первом попадании в состояние можно установить те или иные параметры объекта. Например, его начальное положение в этом состоянии и alpha-прозрачность (если они отличны от общих параметров в разделе obj):
<init x="100" y="0" al="0" />
Кроме этого, машина состояний может изменять состояния других объектов (не только машин).
<set obj="2" st="show" /> <!-- перевод объекта 2 в состояние show -->
Команды init и set могут повторяться, но при первом попадании выполняется всегда только первый init и первый set. Если необходимо выполнить несколько set-ов, делается это так:
<set obj="2" st="show" set="1"/> <!-- перевод объекта 2 в состояние show и сразу--> <set obj="3" st="show" set="2"/> <!-- перевод объекта 3 в состояние play --> <set obj="4" st="run"/> <!-- перевод объекта 4 и сразу -->
Например, необходимо при заходе в состояние установить 2-й объект в состояние show, затем переместиться на dx и после этого перемещения задать состояние 3-го объекта в play. Делаем это так:
<set obj="2" st="show"/> <!-- перевод объекта 2 в состояние show, затем --> <move dx="100 set="1"/> <!-- перемещаемся и затем выполняем следующую установку --> <set obj="3" st="play"/>
Если в начале не нужна установка состояния, вставляем пустую команду set:
<set/> <move dx="100 set="1"/> <!-- перемещаемся и затем выполняем установку --> <set obj="3" st="play"/>
Таймер
Время пребывания в данном состоянии задается командой time с параметром tm в ms:
<time tm="1000 go="next"/> <!-- ждем 1000ms и покидаем состояние -->
Для программирования объектов со случайным поведением можно использовать последовательность команд time с параметром вероятности перехода p:
<time go="state1" tm="100" p="0.5" /> <!-- через 100ms с вероятностью 1/2 перейдем в state1 --> <time go="state2"/> <!-- иначе перейдем в state2 -->
Анимация объекта
Кроме фреймовой анимации возможно управление alpha-каналом, перемещением по сцене, вращением и т.п. Например, пусть объект должен переместиться в целевую точку с координатами tx,ty со скоростью 0.1 пиксель в ms, увеличивая свою непрозрачность с начальной (из раздела init) до ta=1 со скоростью da в ms. При этом он вращается и уменьшается:
<move tx="100" ty="200" v="0.1" /> <alpha ta="1.0" da="0.001" /> <spin ta="0.1" da="0.001" /> <scale ts="0.1" ds="0.001" />
Любых команд движения может быть несколько. Например, пусть в данном состоянии объект должен двигаться по квадрату, пока мы на него не кликнем:
<init x="0" y="0" /> <!-- левый верхний угол --> <move tx="10" ty="0" v="0.1" /> <!-- вправо --> <move tx="10" ty="10" v="0.1" /> <!-- вниз --> <move tx="0" ty="10" v="0.1" /> <!-- влево --> <move tx="0" ty="0" v="0.1" loop /> <!-- вверх и повтор --> <click go="next"/>
Флаг loop в последнем теге loop означает, повтор проигрывания тегов move.
При движении (особенно, персонажей) необходимо производить смену фреймов анамимаци. Пусть в 0-й строке таблицы анимаций находится движение вправо, а в 1-й - вниз. Тогда перемещение вправо и вниз будет проводится следующим образом:
<draw res c1="0" c2="15" c="10" r="0" /> <!-- анимация движения вправо --> <draw res c1="0" c2="15" c="10" r="1" /> <!-- анимация движения вниз --> <move tx="10" ty="0" v="0.1" draw/> <!-- вправо --> <move tx="10" ty="0" v="0.1"/> <!-- вниз -->
Флаг draw означает, что по окончании движения происходит перывание выполнения тега draw и переход к следующему тегу в списке тегов draw. Аналогично можно прервать тег вращения spin и т.д.
Симулирование физики
Тег move может быть в нескольких форматах
- (tx,ty,v,a) - где (tx,ty) - целевая точка, v-скорость a-ускорение движения к ней.
- (vx,vy,ax,ay) - где (vx,vy) - начальная скорость, (ax,ay) - ускорение (сила) действующая на объект.
Смоделируем падение объекта до линии ty="0", с последующим его подпрыгиванием три раза с затухающей амплитудой (координата y направлена вниз):
<init x="0" y="0" /> <move vx="0" vy="0" ay="9.8" ty="0"/> <!-- падаем --> <move vx="0.5" vy="-0.5" ay="9.8" ty="0"/> <!-- первый отскок --> <move vx="0.3" vy="-0.3" ay="9.8" ty="0"/> <!-- второй отскок --> <move vx="0.1" vy="-0.1" ay="9.8" ty="0"/> <!-- третий отскок -->
Документация
Любая команда (тег) внутри описания состояния может повторяться. Всегда выполняется сначала первая команда данного типа. Команды инициализации (init), установки состояния другого объекта (set) Переход к следующей команде данного типа осущ
Любой тег изменения (wait, move, spin, alpha, scale) может иметь параметр перехода в новое состояние (go="состояние"). Переход произойдет, если закончится данное изменение (временная пауза, движение и т.д.) и выполнится условие перехода, проверяющееся в каких состояниях находятся другие объекты.
wait tm
Временная задержка
- tm - время в ms задержки; может отсутствовать, тогда сразу происходит проверка условия по объектам.
Примеры:
<-- Ждем время tm и переходим в состояние go: --> <wait tm="1000" go="click"/> <-- Ждем пока объект obj не окажется в состоянии st, затем переходим в состояние go: --> <wait obj="36" st="end" go="click"/> <-- Сначала ждем время tm, а затем состояния объекта: --> <wait tm="1000" obj="36" st="end" go="click"/> <-- Дожидаемся пока несколько объектов окажутся в своих состояниях: --> <wait tm="1000" go="click"> <and obj="36" st="run"> <and obj="37" st="slip"> </wait> <-- (первый объект можно засунуть в wait, как в примерах ранее)-->
Временная задержка
move tx ty v
- tx, ty - целевые координаты в px к которым должен переместиться объект
- v - скороcть перемещения в px/ms. Для фреймовой анимации, например, ходьбы скорость расчитывается исходя из длительности одного кадра и числа кадров на фазу движения. Поэтому задается не время движения, а именно скорость.
Примеры
Часы с маятником
Есть 2 машины: стрелка и маятник в виде шарика, качающегося под стрелкой. При клике на шарик, часы останавливаются. При еще одном клике - опять начинают идти. Просто тикающую стрелку можно сделать из одного состояния "run":
<obj id="1" nm="стрелка" res="1" x="0" y="0" w="10" h="100" px="5" py="100" state="run"> <st id="run"> <rot da="30" tm="100"/> <!-- поворот на 30 град. за 100 ms --> <rot tm="900" rot="0"/> <!-- задержка 900 ms и зацикливание rot-эйтов--> </st> </obj>
Однако, мы хотим останавливать часы только в состоянии окончания движения стрелки (после поворота от цифры к цифре). Кроме этого, нужна синхронность стрелки и маятника (т.е. стрелка поворачивается, когда маятник достигает максимального отклонения вправо). Поэтому:
<obj id="1" nm="стрелка" res="1" x="0" y="0" w="10" h="100" px="5" py="100" > <st id="stop" > <!-- просто рисуем res c текущими параметрами --> </st> <st id="run"> <!-- поворачиваем на 30 град. вокруг (px,py) --> <rot da="30" tm="100" go="stop"/> </st> </obj>
Картинку шарика маятника будем не вращать относительно вынесенной за него точки пивота, а двигать по окружности, т.к. он должен принять на себя клик в своем текущем положении. Простая реализация может выглядеть так:
<obj id="2" nm="шарик" res="1" x="" y="" cx="5" cy="100" state="run"> <st id="run"> <set obj="1" st="run"/> <!-- запускание поворота стрелки --> <spin ta="60" va="0.24" /> <!-- отклоняемся вправо до 60 град. со скоростью va --> <spin ta="-60" va="-0.24"/> <!-- отклоняемся влево --> <spin ta="60" va="0.24"/> <!-- снова вправо --> <spin set="0" spin="0"/> <!-- запускаем поворот стрелки, зацикливаем качание --> <click/> <!-- при клике делаем паузу вращения до следующего клика --> </st> </obj>
Скорость поворота по окружности (град/ms) вычисляется исходя из амплитуды в 60 град. Четыре таких амплитуд (качание влево-вправо) должны произойти за 1000 ms. Поэтому va=60*4/1000 = 0.24.
Добавим маятнику физичности. Мы ходим, чтобы после остановки и последующего клика он падал "вниз", а не начинал всё время поворачиваться вправо.
<obj id="2" nm="шарик" res="1" x="" y="" cx="5" cy="100" state="right"> <st id="right"> <!-- движемся cправа --> <set /> <!-- первый раз стрелку не трогаем --> <set obj="1" st="run"/> <!-- переключаем из spin --> <spin ta="60" va="0.24" set="1"/> <!-- отклонившись, включаем стрелку--> <spin ta="0" va="-0.24" go="left" /> <click spin="1"/> </st> <st id="left"> <!-- движемся влево --> <spin ta="-60" va="-0.24" /> <!-- отклоняемся до 60 град. со скоростью va --> <spin ta="0" va="0.24" go="right" /> <click spin="1"/> </st> </obj>