Статьи

Сила конечных автоматов: применение и расширение

Вот заключительная часть нашего урока о создании автомобиля с несколькими состояниями с использованием конечного автомата . В этой части вы увидите, как легко добавить больше состояний, а также новые функции автомобиля. Когда вы закончите, вы будете точно знать, как структурировать управление для вашего объекта FSM, что будет продемонстрировано, когда мы добавим анимацию и звук.


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


Вот таблица переходов между штатами (STT), которую мы нарисовали в первой части урока:

Конечный шаблон проектирования в AS3

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

Конечный шаблон проектирования в AS3

Попробуйте ввести правильные действия для вновь добавленных состояний.

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

Конечный шаблон проектирования в AS3

Следующее, что вам нужно сделать, это добавить два новых действия с состоянием в ваш интерфейс IState.

Откройте IState.as в FD и включите код ниже. Я бы добавил его сразу после driveForward() .

1
2
function driveBackward ():void;
function driveReallyFast ():void;

Теперь запустите приложение и посмотрите, что будет дальше.

Конечный шаблон проектирования в AS3

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

Добавьте следующий код ко всем существующим классам состояний. Поместите его прямо под driveForward() . Давайте попросим их вызвать print(this + "apply action here") чтобы напомнить нам, что нам нужно вернуться к ним после создания двух других классов состояния.

1
2
3
4
5
6
7
8
9
public function driveBackward ():void
{
    _car.print (this + » apply action here»);
}
 
public function driveReallyFast ():void
{
    _car.print (this + » apply action here»);
}

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


Откройте класс Car, перейдите к методу initializeStates() и добавьте приведенный ниже код.

1
2
_engineDriveBackwardState = new EngineDriveBackward (this);
_engineDriveReallyFastState = new EngineDriveReallyFast (this);

Ни переменных, ни классов не существует, но мы это исправим. Сначала войдите в слово «EngineDriveBackward», затем нажмите «CTRL + SHIFT + 1»; выделите «Создать новый класс», затем нажмите «ENTER».

Конечный шаблон проектирования в AS3

Убедитесь, что сопоставили всю информацию выше, прежде чем нажать «ОК». У вас должно быть что-то похожее на код ниже. Не забудьте добавить переменную _car перед конструктором и добавить символ «$» для параметра. Затем сделайте присваивание внутри конструктора.

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
56
57
package com.activeTuts.fsm
{
    public class EngineDriveBackward implements IState
    {
         
        private var _car:Car;
         
        public function EngineDriveBackward($car:Car)
        {
            _car = $car;
        }
         
        /* INTERFACE com.activeTuts.fsm.IState */
         
        public function turnKeyOff():void
        {
             
        }
         
        public function turnKeyOn():void
        {
             
        }
         
        public function driveForward():void
        {
             
        }
         
        public function driveBackward():void
        {
             
        }
         
        public function driveReallyFast():void
        {
             
        }
         
        public function reFuel():void
        {
             
        }
         
        public function update($tick:Number):void
        {
             
        }
         
        public function toString():String
        {
             
        }
         
    }
 
}

Ладно. Теперь вернитесь к классу Car, где мы остановились, и поместите курсор в переменную _engineDriveBackwardState и нажмите «CTRL + SHIFT + 1»; выберите «Объявить личную переменную» и нажмите «ENTER». Это добавит его как последнюю переменную перед конструктором. Измените его тип на «IState». Я бы переместил его туда, где находятся другие переменные состояния, но это необязательно.

Выполните ту же процедуру, чтобы создать класс EngineDriveReallyFast .

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

Автомобиль также должен будет обеспечить внешний доступ к управлению для двух новых действий. Добавьте приведенный ниже код после driveForward() .

1
2
3
4
5
6
7
8
9
public function driveBackward (e:Event = null):void
{
    _currentState.driveBackward ();
}
 
public function driveReallyFast (e:Event = null):void
{
    _currentState.driveReallyFast ();
}

Наконец, добавьте явные методы получения в нижней части вашего класса Car чуть выше changeState() .

1
2
3
public function getEngineDriveBackwardState ():IState { return _engineDriveBackwardState;
 
public function getEngineDriveReallyFastState ():IState { return _engineDriveReallyFastState;

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

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.activeTuts.fsm
{
    public class EngineDriveBackward implements IState
    {
        private var _car:Car;
         
        public function EngineDriveBackward($car:Car)
        {
            _car = $car;
        }
         
        /* INTERFACE com.activeTuts.fsm.IState */
         
        public function turnKeyOff ():void
        {
            _car.print («click… rolling to a stop in reverse…the engine has been turned off.»);
            _car.changeState (_car.getEngineOffState ());
        }
         
        public function turnKeyOn ():void
        {
            _car.print («you’re driving in reverse, no need to crank the ignition!»);
        }
         
        public function driveForward ():void
        {
            _car.print («click, switching from reverse to drive…»);
            _car.changeState (_car.getEngineDriveForwardState ());
        }
         
        public function driveBackward ():void
        {
            _car.print («…already driving in reverse.»);
        }
         
        public function driveReallyFast ():void
        {
            _car.print («click, changing gears from reverse to turbo!!!»);
            _car.changeState (_car.getEngineDriveReallyFastState ());
        }
         
        public function reFuel ():void
        {
            if (_car.hasFullTank () == false)
            {
                _car.print («stopping the car from driving in reverse, changing gear to park, turning the key to the off position, getting out of the car and adding «
                + Number (_car.refillWithFuel ()).toFixed (2) + » gallon(s) of fuel.»);
                _car.changeState (_car.getEngineOffState ());
            }
        }
         
        public function update ($tick:Number):void
        {
            _car.engineTimer += $tick;
             
            if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
            {
                _car.engineTimer -= Car.ONE_SIXTH_SECONDS;
                 
                _car.consumeFuel (Car.REVERSE_FUEL_CONSUMPTION);///25 seconds gas supply
            }
        }
         
        public function toString ():String
        {
            return ‘driving backward’;
        }
         
    }
 
}

Метод update() требует дополнительного свойства для Car. Поместите курсор внутрь REVERSE_FUEL_CONSUMPTION , нажмите «CTRL + SHIFT + 1», выберите «Объявить константу», затем нажмите «ENTER». Это добавляет константу вверху класса Car с типом String по умолчанию. Измените его тип на Number и присвойте ему значение .0066 . Это дает 25 секунд движения задним ходом с полным баком в один галлон. Вы можете переместить это, чтобы быть с другими константами расхода топлива.

Пока вы там, вы также можете добавить константу TURBO_FUEL_CONSUMPTION со значением 0,016 (10 секунд в турбо режиме на один галлон). Мы будем использовать это позже для EngineDriveReallyFast состояния EngineDriveReallyFast .

Это так просто. Продолжайте и заканчивайте класс EngineDriveReallyFast . Если вы застряли, вернитесь и проверьте с вашим STT. Или вы можете просто открыть завершенный класс в папке «StatePatternPartial2», входящей в исходную загрузку, и посмотреть, что вы пропустили.


Добавьте новые действия состояния ( _car.driveBackward() и _car.driveReallyFast() в разделы теста в своем классе «Main», затем запустите приложение.

Проверьте распечатку на соответствие тестовым действиям, если они совпадают.

Заметьте, где написано «отменить действие здесь»? Это говорит нам о том, что мы забыли вернуться и добавить поведение для новых функций Car других классов состояний. Вернитесь к каждому из них и примените поведение, основанное на вашем STT.

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


Загрузите исходный файл в папку с именем «media» и перетащите его в папку «bin» вашего проекта CarFSM. Он содержит все звуковые эффекты и графику.

Затем, также с загрузкой исходного кода, вы найдете папку с именем «code», попадете внутрь нее и в папку «StatePatternComplete». Не трогайте ничего, кроме класса «Media.as». Перетащите его в папку «com.activeTuts.fsm» вместе с оставшимися классами.

Важно, чтобы вы знали, как работает этот класс, поэтому вы можете добавить к нему позже, если решите попробовать домашнее задание добавить еще одну функциональность автомобиля — вернуться обратно в парк. Так что пройдите по крайней мере один раз; Я также объясню свои обязанности:

Он заботится практически обо всем, что касается медиа, от встраивания медиаресурсов до разрешения контроля над ними. Первое, что он делает — это вставляет все графические и звуковые файлы в папку «media». Анимация, визуальный вывод и звук управляются различными классами состояний через экземпляр автомобиля, который, в свою очередь, передает запрос экземпляру носителя. Когда этот экземпляр создан, он делает пять вещей — он добавляет себя в качестве дочернего элемента автомобиля, создает визуальные активы автомобиля, звуки, кнопки, а затем визуальный вывод.

Звуковые манипуляции обычно обрабатываются двумя функциями; например, когда автомобиль работает, если вы установите переключатель «привод», автомобиль вызывает playParkToDrive() который воспроизводит звук автомобиля, увеличивающего мощность, а затем playPeakDrive() когда звук заканчивается для звука с постоянной скоростью движения.


Начиная с верхней части класса Car, добавьте следующий код после _currentState переменной _currentState . Эти свойства появились, когда функции были добавлены в автомобиль. Например, я хотел вернуть передачу для парковки через одну секунду, когда машина либо выключена, либо закончилась, поэтому она есть. Другим примером будет очистка дисплея визуального вывода после 10 секунд печати выписки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public static const ONE_SECOND:int = 1;
         
private static const TEN_SECONDS:int = 10;
 
private var _cleanUpTimer:Number = 0;
 
private var _angle:Number = 0;
 
private var _reallyFastRollSpeed:Number = 63;
private var _driveRollSpeed:Number = 35;
private var _reverseRollSpeed:Number = -15;
private var _currentRollSpeed:Number = 0;
 
private var _carBlurEffect:Sprite;
 
private var _media:Media;

Обязательно добавьте _media = new Media (this); внутри метода init() сразу после вызова метода initializeStates() .


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public function update ($tick:Number):void
{
    _currentState.update ($tick);
     
    _media.setTitle (_currentState.toString ());
     
    if (_currentState == _engineOutOfFuelState || _currentState == _engineOffState)
    {
        if (_currentRollSpeed == 0) //or add this test for the update method of the two states
        {
            _media.stopRollSound ();
        }
    }
     
    //clean up the output panel after 10 seconds
    if ((_cleanUpTimer += $tick) >= TEN_SECONDS)
    {
        _cleanUpTimer -= TEN_SECONDS;
        _media.cleanUp ();
    }
}

Помните — изменения в методах, какими бы сложными они ни были, манипулируют только звуком и анимацией. Если вы планируете выполнить домашнее задание позже (позволяя машине вернуться в парк из любого состояния вождения), вот метод, с которым вам нужно работать. Вставьте его перед turnKeyOn() . Этот метод вызывается при нажатии переключателя «park» (если он включен). Смотрите addButtons() в классе Media.

1
2
3
4
public function returnToPark (e:Event = null):void
{
    //homework
}

От turnKeyOn() до reFuel() добавьте _cleanUpTimer = 0 перед текущим делегированием состояния. И вот так панель визуального вывода очищается через 10 секунд.

consumeFuel() и refillWithFuel() также необходимо обновить, как показано здесь:

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
public function consumeFuel ($consumption:Number):void
{
    if ((_fuelSupply -= $consumption) <= 0)
    {
        _fuelSupply = 0;
        _cleanUpTimer = 0;
         
        stopEngine ();
         
        if (currentRollSpeed != 0)
        {
            playRoll ();
            print («the engine has stopped, no more fuel to run…rolling to a stop.»);
        }
        else print («the engine has stopped, no more fuel to run…»);
         
        _media.setColor (0xFF0000);
         
        changeState (_engineOutOfFuelState);
    }
}
 
public function refillWithFuel ():Number
{
    if (_currentRollSpeed != 0) playRoll ();
     
    var neededSupply:Number = _fuelCapacity — _fuelSupply;
    _fuelSupply += neededSupply;
    _media.setColor ();
    return neededSupply;
}

Я просто продолжал добавлять любую функцию, о которой мог подумать, чтобы показать гибкость State Pattern.

Замените _media.print ($text); метода print() на _media.print ($text); ,


Перейдите на одну строку после метода toString() в нижней части класса и вставьте список ниже.

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
56
57
58
59
60
61
62
63
64
65
66
67
///Sound control
public function startEngine ():void
{
    _media.startEngine ();
}
 
public function startEngineNoFuel ():void
{
    _media.startEngineNoFuel ();
}
 
public function startEngineWhileRunning ():void
{
    _media.startEngineWhileRunning ();
}
 
public function keyOffClick ():void { _media.keyOffClick ();
 
public function stopEngine ():void
{
    _media.stopEngine ();
}
 
public function playParkToDrive (e:Event = null):void
{
    _media.playParkToDrive (e);
}
 
public function playDriveToTurbo (e:Event = null):void
{
    _media.playDriveToTurbo (e);
}
 
public function playParkToReverse (e:Event = null):void
{
    _media.playParkToReverse (e);
}
 
public function playReverseToDrive ():void
{
    _media.playReverseToDrive ();
}
 
public function playDriveToReverse ():void
{
    _media.playDriveToReverse ();
}
 
public function playReverseToTurbo ():void
{
    _media.playReverseToTurbo ();
}
 
public function playTurboToReverse ():void
{
    _media.playTurboToReverse ();
}
 
public function playTurboToDrive ():void
{
    _media.playTurboToDrive ();
}
 
public function playRoll ():void
{
    _media.playRoll ();
}

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

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

Далее идет управление радиокнопкой «парк». Нам нужны эти методы, чтобы автоматически вернуть передачу в режим парковки, если двигатель выключен или отсутствует топливо (через одну секунду). Не путайте его с returnToPark() который вызывается переключателем (см. Шаг 29). Вставьте код дальше. Вы увидите, как эти методы были задуманы, когда мы начнем добавлять к классам состояний.

1
2
3
4
5
6
7
8
9
public function switchToPark ():void
{
    _media.parked = true;
}
 
public function isParked ():Boolean
{
    return _media.parked;
}

Код ниже завершает класс. Добавьте его в конце и сохраните свою работу.

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
///Animation control
public function get currentRollSpeed ():Number { return _currentRollSpeed;
 
public function set currentRollSpeed ($value:Number):void
{
    _currentRollSpeed = $value;
}
 
public function get carFrontWheel ():Sprite { return _media.carFrontWheel;
 
public function get carRearWheel ():Sprite { return _media.carRearWheel;
 
public function get carBody ():Sprite { return _media.carBody;
 
public function get carBlurEffect ():Sprite { return _media.carBlurEffect;
 
public function get angle ():Number { return _angle;
 
public function set angle (value:Number):void { _angle = value;
 
public function get reallyFastRollSpeed ():Number { return _reallyFastRollSpeed;
 
public function get reverseRollSpeed ():Number { return _reverseRollSpeed;
 
public function get driveRollSpeed ():Number { return _driveRollSpeed;

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


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

Просмотрите и сравните реализации метода IState для вашей текущей версии EngineDriveReallyFast с приведенным ниже кодом.

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public function turnKeyOff ():void
{
    _car.print («click… rolling to a stop from turbo…the engine has been turned off.»);
    _car.stopEngine ();
    _car.keyOffClick ();
    if (_car.currentRollSpeed != 0) _car.playRoll ();
    _car.changeState (_car.getEngineOffState ());
}
 
public function turnKeyOn ():void
{
    _car.print («man, we’re on turbo, don’t crank the ignition!»);
    _car.startEngineWhileRunning ();
}
 
public function driveForward ():void
{
    _car.print («slowing down to regular driving speed…»);
    _car.playTurboToDrive ();
    _car.changeState (_car.getEngineDriveForwardState ());
}
 
public function driveBackward():void
{
    _car.print («click, switching the gears from turbo to reverse…»);
    _car.playTurboToReverse ();
    _car.changeState (_car.getEngineDriveBackwardState ());
}
 
public function driveReallyFast ():void
{
    _car.print («We can’t drive any faster than this, no changes…»);
}
 
public function reFuel ():void
{
    if (_car.hasFullTank () == false)
    {
        _car.stopEngine ();
        _car.print («hitting the brakes!, stopping the car, switching gear to park, turning the key to the off position, getting out of the car and adding «
        + Number (_car.refillWithFuel ()).toFixed (2) + » gallon(s) of fuel.»);
        _car.changeState (_car.getEngineOffState ());
    }
}
 
public function update ($tick:Number):void
{
    _car.engineTimer += $tick;
     
    //if the speed goes over regular driving speed, show the turbo blur effect
    if (_car.currentRollSpeed > _car.driveRollSpeed)
    {
        if (_car.carBlurEffect.alpha < .6) _car.carBlurEffect.alpha += .01;
        if (_car.carBlurEffect.x > -30) _car.carBlurEffect.x += -.01;
         
        //rotate the car body clockwise a little
        if (_car.carBody.rotation < 7) _car.carBody.rotation += .05;
         
        //move the car body a little to the left
        if (_car.carBody.x > -5) _car.carBody.x += -.02;
    }
     
    //slowly increase speed if not maxed
    if (_car.currentRollSpeed < _car.reallyFastRollSpeed) _car.currentRollSpeed += .2;
    else _car.currentRollSpeed = _car.reallyFastRollSpeed;
     
    _car.carFrontWheel.rotation += _car.currentRollSpeed;
    _car.carRearWheel.rotation += _car.currentRollSpeed;
     
    //bobbing motion
    _car.carBody.y = Math.sin (_car.angle += .05) * 2;
     
    if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    {
        _car.engineTimer -= Car.ONE_SIXTH_SECONDS;
         
        _car.consumeFuel (Car.TURBO_FUEL_CONSUMPTION);
    }
}

Я исключил конструктор и метод toString() так как они ничего не делают со средствами массовой информации.

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

Анимация обрабатывается с помощью метода update() . Остальное просто заботится о воспроизведении звука.


Теперь для противоположного конца.

Просмотрите, сравните и внесите изменения, как мы сделали для состояния EngineDriveReallyFast.

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public function turnKeyOff ():void
{
    _car.print («click… rolling to a stop in reverse…the engine has been turned off.»);
    _car.stopEngine ();
    _car.keyOffClick ();
    if (_car.currentRollSpeed != 0) _car.playRoll ();
    _car.changeState (_car.getEngineOffState ());
}
 
public function turnKeyOn ():void
{
    _car.print («you’re driving in reverse, no need to crank the ignition!»);
    _car.startEngineWhileRunning ();
}
 
public function driveForward ():void
{
    _car.print («click, switching from reverse to drive…»);
    _car.playReverseToDrive ();
    _car.changeState (_car.getEngineDriveForwardState ());
}
 
public function driveBackward ():void
{
    _car.print («…already driving in reverse.»);
}
 
public function driveReallyFast ():void
{
    _car.print («click, changing gears from reverse to turbo!!!»);
    _car.playReverseToTurbo ();
    _car.changeState (_car.getEngineDriveReallyFastState ());
}
 
public function reFuel ():void
{
    if (_car.hasFullTank () == false)
    {
        _car.stopEngine ();
        _car.print («stopping the car from driving in reverse, changing gear to park, turning the key to the off position, getting out of the car and adding «
        + Number (_car.refillWithFuel ()).toFixed (2) + » gallon(s) of fuel.»);
        _car.changeState (_car.getEngineOffState ());
    }
}
 
public function update ($tick:Number):void
{
    _car.engineTimer += $tick;
     
    ///visual
    if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.01;
    if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
     
    if (_car.carBody.rotation > -5) _car.carBody.rotation += -.03;
    if (_car.carBody.x < 3) _car.carBody.x += .02;
     
    if (_car.currentRollSpeed > _car.reverseRollSpeed) _car.currentRollSpeed += -.4;
    else _car.currentRollSpeed = _car.reverseRollSpeed;
     
    _car.carFrontWheel.rotation += _car.currentRollSpeed;
    _car.carRearWheel.rotation += _car.currentRollSpeed;
     
    _car.carBody.y = Math.sin (_car.angle += .1) * 1;
     
    if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    {
        _car.engineTimer -= Car.ONE_SIXTH_SECONDS;
         
        _car.consumeFuel (Car.REVERSE_FUEL_CONSUMPTION);
    }
}

Ничего особенного, мы просто создаем впечатление, будто машина едет задним ходом. Поскольку предыдущим состоянием может быть EngineDriveReallyFast , мы должны убедиться, что эффект турбо-размытия EngineDriveReallyFast (строки 51-52).


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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public function turnKeyOff ():void
{
    _car.print («click… rolling to a stop…the engine has been turned off.»);
    _car.stopEngine ();
    _car.keyOffClick ();
    if (_car.currentRollSpeed != 0) _car.playRoll ();
    _car.changeState (_car.getEngineOffState ());
}
 
public function turnKeyOn ():void
{
    _car.print («you’re driving so don’t crank the ignition!»);
    _car.startEngineWhileRunning ();
}
 
public function driveForward ():void
{
    _car.print («already driving — no need to change anything…»);
}
 
public function driveBackward():void
{
    _car.print («click, changing the gear from drive to reverse…»);
    _car.playDriveToReverse ();
    _car.changeState (_car.getEngineDriveBackwardState ());
}
 
public function driveReallyFast ():void
{
    _car.print («switching from drive to turbo!!!»);
    _car.playDriveToTurbo ();
    _car.changeState (_car.getEngineDriveReallyFastState ());
}
 
public function reFuel ():void
{
    if (_car.hasFullTank () == false)
    {
        _car.stopEngine ();
        _car.print («stopping the car, changing gears to park, turning the key to the off position, getting out of the car and adding «
        + Number (_car.refillWithFuel ()).toFixed (2) + » gallon(s) of fuel.»);
        _car.changeState (_car.getEngineOffState ());
    }
}
         
public function update ($tick:Number):void
{
    _car.engineTimer += $tick;
     
    ///visual
    if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.01;
    if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
     
    if (_car.carBody.rotation < 5) _car.carBody.rotation += .01;
    else if (_car.carBody.rotation > 5) _car.carBody.rotation += -.01;
     
    if (_car.carBody.x > -3) _car.carBody.x += -.01;
    else if (_car.carBody.x < -3) _car.carBody.x += .01;
     
    if (_car.currentRollSpeed < _car.driveRollSpeed) _car.currentRollSpeed += .2;
    else if (_car.currentRollSpeed > _car.driveRollSpeed) _car.currentRollSpeed += -.2;
    else _car.currentRollSpeed = _car.driveRollSpeed;
     
    _car.carFrontWheel.rotation += _car.currentRollSpeed;
    _car.carRearWheel.rotation += _car.currentRollSpeed;
     
    _car.carBody.y = Math.sin (_car.angle += .1) * 2;
     
    if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    {
        _car.engineTimer -= Car.ONE_SIXTH_SECONDS;
         
        _car.consumeFuel (Car.DRIVE_FUEL_CONSUMPTION);
    }
}

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


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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public function turnKeyOff ():void
{
    _car.print («click… the engine has been turned off from park.»);
     
    ///visual
    _car.carBody.rotation = 0;
    _car.carBody.x = 0;
     
    _car.stopEngine ();
    _car.keyOffClick ();
    _car.changeState (_car.getEngineOffState ());
}
 
public function turnKeyOn ():void
{
    _car.print («the engine’s already running you didn’t have to crank the ignition!»);
    _car.startEngineWhileRunning ();
}
 
public function driveForward ():void
{
    _car.print («click, changing gears to drive …now were going somewhere…»);
    _car.playParkToDrive ();
    _car.changeState (_car.getEngineDriveForwardState ());
}
 
public function driveBackward():void
{
    _car.print («click, changing gears from park to reverse…»);
    _car.playParkToReverse ();
    _car.changeState (_car.getEngineDriveBackwardState ());
}
 
public function driveReallyFast ():void
{
    _car.print («click, going on turbo…woohoo!!!»);
    _car.playDriveToTurbo ();
    _car.changeState (_car.getEngineDriveReallyFastState ());
}
 
public function reFuel ():void
{
    if (_car.hasFullTank () == false)
    {
        _car.stopEngine ();
        _car.print («turning the car off, getting out and adding » + Number (_car.refillWithFuel ()).toFixed (2) + » gallon(s) of fuel.»);
        _car.changeState (_car.getEngineOffState ());
    }
}
 
public function update ($tick:Number):void
{
    _car.engineTimer += $tick;
     
    ///visual
    _car.angle += 2;
     
    //vibration effect
    _car.carBody.rotation = Math.cos (_car.angle) * .8;
     
    if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.005;
    if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
     
    if (_car.currentRollSpeed > .3) _car.currentRollSpeed += -.3;
    else if (_car.currentRollSpeed < -.3) _car.currentRollSpeed += .3;
    else _car.currentRollSpeed = 0;
     
    if (_car.currentRollSpeed == 0) _car.switchToPark ();
     
    _car.carFrontWheel.rotation += _car.currentRollSpeed;
    _car.carRearWheel.rotation += _car.currentRollSpeed;
     
    if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    {
        _car.engineTimer -= Car.ONE_SIXTH_SECONDS;
         
        _car.consumeFuel (Car.IDLE_FUEL_CONSUMPTION);
    }
}

Шаблон начинает показывать. Познакомьтесь с изменениями и примените их к своему классу.


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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public function turnKeyOff ():void
{
    _car.print («you already did this when the fuel ran out earlier…»);
}
 
public function turnKeyOn ():void
{
    _car.print («no fuel — the car will not start, get some fuel before anything. Returning the key to the off position.»);
    _car.startEngineNoFuel ();
}
 
public function driveForward ():void
{
    _car.engineTimer = 0;
    _car.print («click, changing the gear to drive doesn’t do anything…the car has no fuel, returning the gear to park…»);
}
 
public function driveBackward ():void
{
    _car.engineTimer = 0;
    _car.print («click, changing the gear to reverse won’t do anything either…the car has no fuel, returning the gear to park…»);
}
 
public function driveReallyFast ():void
{
    _car.engineTimer = 0;
    _car.print («click, changed gear to turbo, no change, the car is not running, try to get som fuel, returning the gear to park.»);
}
 
public function reFuel ():void
{
    if (_car.hasFullTank () == false)
    {
        _car.print («getting out of the car and adding » + _car.refillWithFuel () + » gallon(s) of fuel.\n»);
        _car.changeState (_car.getEngineOffState ());
    }
}
 
public function update ($tick:Number):void
{
    if (_car.currentRollSpeed == 0)
    {
        if (! _car.isParked ())
        {
            _car.engineTimer += $tick;
             
            if (_car.engineTimer >= Car.ONE_SECOND)
            {
                _car.switchToPark ();
            }
        }
    }
     
    if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.005;
    if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
     
    if (_car.carBody.rotation > 0) _car.carBody.rotation += -.02;
    else if (_car.carBody.rotation < 0) _car.carBody.rotation += .02;
     
    if (_car.carBody.x < 0) _car.carBody.x += .02;
    else if (_car.carBody.x > 0) _car.carBody.x += -.02;
     
    if (_car.currentRollSpeed > 0.3) _car.currentRollSpeed += -.3;
    else if (_car.currentRollSpeed < -0.3) _car.currentRollSpeed += .3;
    else _car.currentRollSpeed = 0;
     
    _car.carFrontWheel.rotation += _car.currentRollSpeed;
    _car.carRearWheel.rotation += _car.currentRollSpeed;;
     
}

Еще одно государство … =)


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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public function turnKeyOff ():void
{
    _car.print («The car’s already off, you can’t turn the key counter-clockwise any further…»);
}
 
public function turnKeyOn ():void
{
    _car.print («Turning the car on…the engine is now running!»);
    _car.startEngine ();
    _car.changeState (_car.getEngineOnState ());
     
}
 
public function driveForward ():void
{
    _car.engineTimer = 0;
    _car.print («click, changing the gear to drive doesn’t do anything…the car is not running, returning the gear to park…»);
}
 
public function driveBackward ():void
{
    _car.engineTimer = 0;
    _car.print («click, changing the gear to reverse does nothing, the car is not running, returning the gear to park…»);
}
 
public function driveReallyFast ():void
{
    _car.engineTimer = 0;
    _car.print («click, changing the gear to turbo, nothing happens, the engine’s off, returning the gear to park…»);
}
 
public function reFuel ():void
{
    if (_car.hasFullTank () == false)
    {
        _car.print («getting out of the car and adding » + Number (_car.refillWithFuel ()).toFixed (2) + » gallon(s) of fuel.»);
    }
}
 
public function update ($tick:Number):void
{
    if (_car.currentRollSpeed == 0)
    {
        if (! _car.isParked ())
        {
            _car.engineTimer += $tick;
             
            if (_car.engineTimer >= Car.ONE_SECOND)
            {
                _car.switchToPark ();
            }
        }
    }
     
    if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.005;
    if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
     
    if (_car.carBody.rotation > 0) _car.carBody.rotation += -.02;
    else if (_car.carBody.rotation < 0) _car.carBody.rotation += .02;
     
    if (_car.carBody.x < 0) _car.carBody.x += .02;
    else if (_car.carBody.x > 0) _car.carBody.x += -.02;
     
    if (_car.currentRollSpeed > 0.3) _car.currentRollSpeed += -.3;
    else if (_car.currentRollSpeed < -0.3) _car.currentRollSpeed += .3;
    else _car.currentRollSpeed = 0;
     
    _car.carFrontWheel.rotation += _car.currentRollSpeed;
    _car.carRearWheel.rotation += _car.currentRollSpeed;
}

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


Замените содержимое вашего класса Main.as с кодом ниже.

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
package com.activeTuts.fsm
{
     
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.utils.getTimer;
     
    [SWF (width = 500, height = 350, frameRate = 60, backgroundColor = 0xFFFFFF)]
     
    public class Main extends Sprite
    {
        private var _past:Number;
        private var _present:Number;
        private var _tick:Number;
        private var _car:Car;
         
        public function Main ()
        {
            init ();
        }
         
        private function init():void
        {
            _present = getTimer ();
            _past = _present;
             
            addCar ();
             
            addEventListener (Event.ENTER_FRAME, update);
        }
         
        private function addCar ():void
        {
            _car = new Car;
            _car.x = stage.stageWidth * .5;
            _car.y = stage.stageHeight * .5;
            addChild (_car);
        }
         
        private function update (e:Event):void
        {
            _present = getTimer ();
            _tick = (_present — _past) * .001;
            _past = _present;
             
            _car.update (_tick);
        }
         
    }
     
}

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

Вы заметите, что у машины слишком мало бензина. Измените значение _fuelCapacity на 3 внутри Car (объявление переменной), а затем тщательно протестируйте Car.

Поздравляем! Вы закончили урок.


Давайте теперь рассмотрим всю процедуру создания полнофункционального объекта FSM с нуля.

  1. Нарисуйте таблицу переходов состояний для вашего объекта.
  2. Создайте свой процедурный объект FSM.
  3. Когда Procedural FSM сработает, если вам нужно добавить гораздо больше функций и / или состояний, преобразуйте его в Шаблон состояний.
  4. Сначала создайте свой интерфейс IState.
  5. Создайте первый / стандартный класс состояний (см. Таблицу переходов состояний и Процедурные действия FSM).
  6. Дублируйте копию своего процедурного объекта FSM, затем разрешите публичный доступ ко всем свойствам, которые должны контролировать классы состояний.
  7. Создайте остальные государственные классы.
  8. Добавить функции / состояния в соответствии с вашими требованиями. Они обычно представляются, когда вы работаете над действиями своего государства.
  9. При добавлении новых состояний начните с таблицы переходов между штатами.
  10. Сначала запрограммируйте действие / состояния / состояния в интерфейсе IState, а затем создайте состояние / состояния.
  11. Add the new action/s to the already existing states manually.
  12. When adding effects (logic) start with the state with biggest change. Then work on the opposite end so you can expect what could happen in the states between.
  13. Polish until logic for all the states meet your specifications.

Care to do the homework? Hint: You won’t need to create another state.

Until next time! Thank you so much for reading!! =)

Comments are always welcome. Please post them below.