Machine

Материал из wiki.appsalutecreator.com
Версия от 15:11, 9 февраля 2012; Steps (обсуждение | вклад) (Общие принципы)
Перейти к: навигация, поиск

Машина состояний (state machine) позволяет описывать сложное поведение игровых объектов. Для машин с типовым набором состояний и поведений вводятся отдельные типы объектов. Например, кнопка, по своей сути, является машиной состояний.

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


Введение

Рассмотрим пример простой машины состояний. Пусть объект (лампочка) находится в состоянии "off". При клике на него мышкой, он переходит в состояние "on", в котором находится 3000 ms, через которые возвращается в состояние "off". В каждом состоянии рисуется своя картинка.

Для создания такой машины, в редакторе, в панеле свойств необходимо выбрать тип объекта "machine", после чего нажать на многоточие в последнем свойстве "states". Появится окно редактирования свойств машины:

On off prop.png

Нажав дважды кнопку "Добавить состояние", мы получим два списка со свойствами этих состояний. Зададим им имена, редактируя первое поля "имя". Далее, на имени первого состояния нажимаем правую кнопку мыши, и из списка команд выбираем первую - "draw". Она появится в списке свойств данного состояния. Кликнув на многоточие в поле ввода "draw", получим ещё одно окно со списком возможных параметров этой команды. Помечая галочкой параметр "res", и нажимая кнопку "Ok", мы увидим его в окне свойств состояния. Действуя аналогичным образом, формируем окно описания состояний в следующем виде:

On off.png

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

On off st.png

Кружки обозначают состояния (у нас их два). Стрелки - переходы между состояниями. Нижняя стрелка - это переход в результате внешнего воздействия (клика мышкой на машину). В окне редактирования состояний это воздействие описывается командой click с единственным параметром go, обозначающим в какое состояние необходимо перейти. Верхняя стрелка - переход в результате процессов внутри состояния on. В данном случае, процесс один - wait. Начав работать, эта команда ждет время t=3000 миллисекунд (3 секунды), а затем выполняет обратный переход в соответствии с параметром go="off".

В каждом состоянии рисуются различные картинки для отображения машины. Этим заведуют команды draw, в единственный параметр которых res, перетаскивается соответствующая картинка из редактора ресурсов.

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

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

st = "off"                           // состояние выключенной лампочки
{
   draw  { res="lamp_off"        }   // рисовать картинку выключенной лампы
   click { go="on"               }   // при клике - идем в состояние "on"
}

st = "on"                            // состояние включенной лампочки
{
   draw  { res="lamp_on"          }  // рисовать картинку включенной лампы
   wait  { t=3000        go="off" }  // через 3 секунды возвращаемся в "off"
}

Фигурные скобки { ... } после имени команд ограничивают список параметров команды. Состояние, начинающееся с записи st="имя состояния", ограничивает фигурными скобками все команды этого состояния. После двух слешей идет пояснение (комментарий).

Общие принципы

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

Файл:Def states.png.png

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

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

  • Инициализации:
    • draw - установка графического ресурса
    • init - инициализация параметров машины (координаты и т.п.)
    • set - установка состояния другого объекта
  • Процессов:
    • wait - задержка по времени
    • move - движение
    • alpha - изменение прозрачности
    • scale - изменение размера
    • rot - вращение вокруг точки пивота
    • phys - движение в силовом поле.
  • Воздействия:
    • click - что делать при клике на машине
    • over - над машиной находится мышь
    • drop - на машине отпущена клавиша мыши
    • drag - машину схватили и тащат
    • throw - машину схватили и кинули
    • apply - сработает при пересечении машины с линией или объектом

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

Взаимодействия машин

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

Lamp switcher.png

Лампочка может находится в двух состояниях: не светится ("dark") и светится ("light"). Аналогично, выключатель имеет два состояния включен ("on") и выключен ("off"). Пусть лампочка, как и раннее горит 3 секунды и тухнет. Однако загореться она может только, если выключатель включен. Кроме этого, выключатель, переходя в состояние "on", зажигает лампочку. Граф взаимодействующих машин выглядит следующим образом:

Lamp switcher st.png

Опишем состояния выключателя:

st = "on"                              // выключатель включен
{
   draw  { res="sw_on"              }  // картинка включенного выключателя
   set   { obj="лампа"   st="light" }  // установить объект лампочка в состояние light
   click { go="off"                 }  // при клике - идем в состояние "off"
}

st = "off"                             // выключатель выключен
{
   draw  { res="sw_off"   }            // картинка выключенного выключателя
   click { go="on"        }            // при клике - идем в состояние "on"
}

Как и раньше, в каждом состоянии определяются команды инициализации рисования (draw). Кроме этого, при входе в состояние "on", запускается команда set, которая переводит другую машину (лампочку) в некоторое состояние (зажигает её). Наконец, команда click переключает выключатель из одного состояния в другое.

Рассмотрим теперь состояния лампочки:

st = "dark"                            // лампочка не светится
{
   draw  { res="lamp_off"           }  // картинка выключенного выключателя
   click { go ="light"       if=0   }  // при клике - идем в состояние "on"
   if    { obj="выключатель" st="on"}  // условие: находится ли объект obj в состоянии st
}

st = "light"                           // лампочка светится
{
   draw  { res="lamp_on"            }  // рисовать граф.ресурс (картинку) включенного выключателя
   wait  { t=3000         go="dark" }  // при клике - идем в состояние "on"
}

Команда лампочки click дополнена параметром if=0. В этом параметре указывается номер, начиная с нуля, команды if (таких команд может быть много). В команде if происходит проверка, находится ли выключатель в состоянии "on". Только если это условие выполняется, происходит переход по go. Если же выключатель выключен, клик на лампочке не приведёт к смене состояния.

Последовательность команд

Процессы внутри состояния могут менять положение машины на сцене (move), поворачивать её (rot), менять прозрачность (alpha) и т.д. Команд данного типа в состоянии может быть несколько. Например, движение вниз на 100 пикселей и вправо на 200 можно реализовать при помощи двух состояний:

st = "down"                          // движемся вниз
{
   move { dy=100 t=1000 go="left" }  // перемещаемся по y вниз за время t, затем go
}

st = "left"                          // движемся вправо
{
   move { dx=200 t=2000 }            // перемещаемся по x за время t и останавливаемся
}

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

Такое же движение можно реализовать при помощи только одного состояния, объединив две команды движения:

st = "move"                          // комбинированное движение
{
   move { dy=100  t=1000  }          // перемещаемся по y вниз за время t
   move { dx=200  t=2000  }          // затем движемся по x за время t и останавливаемся
}

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

Все команды данного типа выполняются последовательно. Цепочки команд разного типа выполняются независимо и параллельно друг другу.

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

   move rot alpha move alpha move

то, независимо от их чередования, они выстраиваются в параллельные списки процессов:

   move  rot    alpha
   move         alpha
  move

Эти списки начинают одновременно выполняться сверху вниз (отрабатывают команды move, одновременно с ними rot и alpha).

Def procesess.png

Например, если при описанном выше движении, объект должен одновременно вращаться на угол da за время t, то необходимо добавить команду вращения:

st = "move"                    // комбинированное движение и вращение
{
   move { dy=100  t=1000  }    // перемещаемся по y вниз за время t
   move { dx=200  t=2000  }    // затем движемся по x за время t и останавливаемся
   rot  { da=360  t=3000  }    // поворачивается на 360 градусов за 3 секунды
}

В данном случае, объект начнет движение и одновременно будет поворачиваться. После окончания движения объект совершит полный оборот вокруг своей точки пивота. Если бы длительность t команды rot была больше 3000 ms, то объект, переместившись вниз и вправо, остановился, но продолжил свое вращение, пока не повернулся бы на 360 градусов.

Циклы и прерывания

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

st = "move_square"                 // движение по сторонам квадрата
{
   move { dy= 100 t=1000        }  // вниз
   move { dx= 100 t=1000        }  // вправо
   move { dy=-100 t=1000        }  // вверх
   move { dx=-100 t=1000 loop=2 }  // влево
}

Параметр loop в последней команде move сообщает процессу, что ему нужно повториться ещё 2 раза с самого начала (с первой команды move). Таким образом, войдя в состояние и пройдя все команды move, машина один раз пройдет по сторонам квадрата. Затем, благодаря параметру loop, сделает это ещё 2 раза. Если loop=-1, то движение будет бесконечным. В примере выше, этого же можно было добиться, поставив в последней команде move, вместо loop=-1 параметр go="move_square" (перезапустить состояние). Кроме команды зацикливания данного процесса, можно из одних команд запускать другие (начинать процессы другого типа). Например, пусть объект движется вправо 1000 ms и вращается 2000 ms. Закончив полный оборот (на уже остановившемся объекте), он начинает снова двигаться вправо. Реализуется это так:

st = "move_rot"                            // движение и вращение
{
   move { dx= 100 t=1000                }  // сдвигаемся
   rot  { da=360  t=2000 loop=-1 move=0 }  // вращаемся
}

Команда вращения rot бесконечно зациклена (loop=-1). Кроме этого, параметр команды вращения move=0 запускает процессы движения сначала (с нулевой команды move). Напоминаем, что нумерация команд начинается с 0. Подобные параметры перезапуска процесса с некоторой команды можно вызывать из любой команды. Имена этих параметров совпадают с именами команд-процессов. Еще один параметр, управляющий ходом выполнения команд процесса - это break. Если этот параметр встречается в команде, то по её завершению, процесс останавливается и не передает дальнейшего управления следующим командам этого типа. При помощи прерывания можно выполнять различные процессы последовательно. Например, пусть мы хотим, чтобы объект переместился вправо и остановился. Затем повернулся, переместился влево и снова повернулся и т.д. Это можно реализовать следующим образом:

st = "move_rot"                             // туда-сюда
{
   rot { break }                           // 0: не вращаемся (da и t не заданы)
   move{ dx= 100 t=1000 break  rot= 1 }    // 0: сдвигаемся и останавливаемся
   rot { da=360  t=2000 break  move=1 }    // 1: вращаемся
   move{ dx=-100 t=1000        rot =2 }    // 1: сдвигаемся и останавливаемся
   rot { da=360  t=2000        move=0 }    // 2: вращаемся
}

Для наглядности, команды записаны в чередующемся порядке (так как они будут выполняться). Первая команда rot, с единственным параметром break, сразу приводит к прерыванию команд вращения. В последних командах rot и move параметр break можно не ставить, так как по окончанию процесса данного типа он прерывается по умолчанию (если нет loop или go). В списке свойств состояния команда break является чекбоксом, который, по умолчанию, помечен. Если он не помечен (или команды break нет), прерывания не происходит (пока вместо чекбокса нужно поставить 1).