1. 程式人生 > >[翻譯]:Artificial Intelligence for games 5.3 STATE MACHINES:狀態機

[翻譯]:Artificial Intelligence for games 5.3 STATE MACHINES:狀態機

目錄

Chapter 5 Decision Making

5.3 STATE MACHINES:狀態機

Often, characters in a game will act in one of a limited set of ways. They will carry on doing the same thing until some event or influence makes them change. A covenant warrior in Halo [Bungie Software, 2001], for example, will stand at its post until it notices the player, then it will switch into attack mode, taking cover and firing.

通常來說,遊戲中的角色所進行的行為將會從有限的行為集合中選取。它們將會保持這個狀態,直到一些事件或者影響使得它們改變。以 光環:戰鬥進化 為例,敵人將會一直駐守直到它們注意到玩家,此時將會轉換到攻擊狀態,尋求掩體以及開火。

We can support this kind of behavior using decision trees, and we’ve gone some way to doing that using random decisions. In most cases, however, it is easier to use a technique designed for this purpose: state machines.

我們可以通過決策樹來實現這種行為,並且我們已經採用某種方式使用隨機決策來做到這一點。然而,在大多數情況下,對於這個目標,我們可以採用更為容易的技術設計:狀態機。

State machines are the technique most often used for this kind of decision making and, along with scripting (see Section 5.9), make up the vast majority of decision making systems used in current games.

狀態機是一種經常用於這類決策的技術,它與指令碼一起使用(參見第5.9節),構成當前遊戲中使用的絕大多數(vast majority)決策系統。

State machines take account of both the world around them (like decision trees) and their internal makeup (their state).

狀態機既與它周圍的世界相關(類似於決策樹)又與其內部組成相關(它們自身的狀態)。

A Basic State Machine :一個基本狀態機

In a state machine each character occupies one state. Normally, actions or behaviors are associated with each state. So as long as the character remains in that state, it will continue carrying out the same action.

在狀態機中,每個角色佔據一個狀態。通常,動作或行為與每個狀態相關聯。因此,只要角色保持在該狀態,它將繼續執行相同的動作。

States are connected together by transitions. Each transition leads from one state to another, the target state, and each has a set of associated conditions. If the game determines that the conditions of a transition are met, then the character changes state to the transition’s target state. When a transition’s conditions are met, it is said to trigger, and when the transition is followed to a new state, it has fired.

每個狀態通過狀態轉換聯絡在一起。每個轉換從一個狀態引導到另一個狀態,即目標狀態,並且每個轉換具有一組相關條件。如果遊戲確定滿足轉換的條件,則角色將狀態改變為要轉換的目標狀態。當滿足轉換條件時,它會被觸發,當轉換到新狀態時,它就會被觸發。

Figure 5.13 shows a simple state machine with three states: On Guard, Fight, and Run Away. Notice that each state has its own set of transitions.

圖5.13顯示了一個具有三種狀態的簡單狀態機:On Guard,Fight和Run Away。請注意,每個狀態都有自己的一組轉換。

The state machine diagrams in this chapter are based on the UML state chart di- agram format, a standard notation used throughout software engineering. States are shown as curved corner boxes. Transitions are arrowed lines, labelled by the condition that triggers them. Conditions are contained in square brackets.

本章中的狀態機圖表基於UML狀態圖表格式,這是整個軟體工程中使用的標準符號。狀態顯示為彎角框。過渡是箭頭線,由觸發它們的條件標記。條件包含在方括號中。

The solid circle in Figure 5.13 has only one transition without a trigger condition. The transition points to the initial state that will be entered when the state machine is first run.

圖5.13中的實心圓只有一個沒有觸發條件的轉換。轉換指向首次執行狀態機時將進入的初始狀態。

image-20181122004651145

You won’t need an in-depth understanding of UML to understand this chapter. If you want to find out more about UML, I’d recommend Pilone [2005].

您不需要深入瞭解UML來理解本章。如果你想了解更多關於UML的資訊,我推薦Pilone [2005]

In a decision tree the same set of decisions is always used, and any action can be reached through the tree. In a state machine only transitions from the current state are considered, so not every action can be reached.

在決策樹中,始終使用相同的決策集,並且可以通過樹到達任何操作。在狀態機中,僅考慮從當前狀態的轉換,因此不是每個動作都可以到達。

Finite State Machines :有限狀態機

In game AI any state machine with this kind of structure is usually called a finite state machine (FSM). This and the following sections will cover a range of increasingly powerful state machine implementations, all of which are often referred to as FSMs.

在遊戲AI中,具有這種結構的任何狀態機通常稱為有限狀態機(FSM)。本節和以下部分將介紹一系列日益強大的狀態機實現,所有這些實現通常都稱為FSM。

This causes confusion with non-games programmers, for whom the term FSM is more commonly used for a particular type of simple state machine. An FSM in computer science normally refers to an algorithm used for parsing text. Compilers use an FSM to tokenize the input code into symbols that can be interpreted by the compiler.

這導致與非遊戲程式設計師的混淆,對於他們來說,術語FSM更常用於特定型別的簡單狀態機。電腦科學中的FSM通常是指用於解析文字的演算法。編譯器使用FSM將輸入程式碼標記為可由編譯器解釋的符號。

The Game FSM :遊戲中的有限狀態機

The basic state machine structure is very general and admits any number of imple- mentations. I have seen tens of different ways to implement a game FSM, and it is rare to find any two developers using exactly the same technique. That makes it difficult to put forward a single algorithm as being the “state machine” algorithm.

基本狀態機結構非常通用,允許任意數量的實現。我已經看到了幾種不同的方法來實現遊戲FSM,並且很少發現任何兩個開發人員使用完全相同的技術。這使得將單個演算法提出為“狀態機”演算法變得困難。

Later in this section, I’ll look at a range of different implementation styles for the FSM, but the main algorithm I work through is just one. I chose it for its flexibility and the cleanness of its implementation.

在本節的後面部分,我將介紹FSM的一系列不同的實現樣式,但我使用的主要演算法只有一個。我之所以選擇它是因為它的靈活性和實現的優雅性。

5.3.1 THE PROBLEM :問題

We would like a general system that supports arbitrary state machines with any kind of transition condition. The state machine will conform to the structure given above and will occupy only one state at a time.

我們想要一個支援具有任何轉換條件的任意狀態機的通用系統。這個狀態機將符合上面給出的結構,並且一次只佔用一個狀態。

5.3.2 THE ALGORITHM :演算法

We will use a generic state interface which can be implemented to include any spe- cific code. The state machine keeps track of the set of possible states and records the current state it is in. Alongside each state, a series of transitions are maintained. Each transition is again a generic interface that can be implemented with the appropriate conditions. It simply reports to the state machine whether it is triggered or not.

我們將使用通用狀態介面,可以實現包含任何特定程式碼。 狀態機跟蹤可能狀態的集合並記錄它所處的當前狀態。在每個狀態下,保持一系列轉換。 每次轉換都是一個通用介面,可以使用適當的條件實現。 它只是向狀態機報告它是否被觸發。

At each iteration (normally each frame), the state machine’s update function is called. This checks to see if any transition from the current state is triggered. The first transition that is triggered is scheduled to fire. The method then compiles a list of actions to perform from the currently active state. If a transition has been triggered, then the transition is fired.

在每次迭代(通常是每個幀),呼叫狀態機的更新函式。 這將檢查是否觸發了當前狀態的任何轉換。 觸發的第一個轉換將會被執行。 然後,該方法維護一個要從當前活動狀態執行的動作列表(actions)。 如果已觸發某個轉換,則會執行這個轉換。

This separation of the triggering and firing of transitions allows the transitions to also have their own actions. Often, transitioning from one state to another also involves carrying out some action. In this case a fired transition can add the action it needs to those returned by the state.

這種轉換的觸發和觸發的分離允許轉換也具有它們自己的動作。 通常,從一個狀態轉換到另一個狀態也涉及執行某些行動。 //在這種情況下,觸發轉換可以將所需的操作新增到狀態返回的操作。

5.3.3 PSEUDO-CODE :虛擬碼實現

The state machine holds a list of states, with an indication of which one is the current state. It has an update function for triggering and firing transitions and a function that returns a set of actions to carry out.

狀態機儲存狀態列表,指示哪一個是當前狀態。 //它具有用於執行和觸發轉換的更新功能以及返回要執行的一組操作的功能。

class machine
{
    // 維護一個狀態機的狀態列表
    list<State> states;
    
    // 初始狀態
    State initialState;
    
    // 當前狀態
    currentState = initialState;
    
    // 檢查並執行轉換,同時返回一個動作列表
    Update()
    {
        // 假定沒有轉換被觸發
        triggeredTransition = None;
        
        // 遍歷所有狀態並儲存第一個被觸發的狀態
        for transition in currentState.getTransitions()
        {
            if transition.isTriggered():
            triggeredTransition = transition;
            break;
        }
        
        // 檢查是否有狀態被觸發?
        if triggeredTransition
        {
            targetState = triggeredTransition.getTargetState();
            
            // 在行為列表之中增加舊狀態的退出操作、狀態轉移的操作以及新狀態的入口操作
            actions = currentState.getExitAction()
            actions += triggeredTransition.getAction()
            actions += targetState.getEntryAction()
            
            // 完成狀態轉移並返回行為列表
            currentState = targetState
            return actions
        }
        // 否則直接返回當前狀態的行為列表
        else return currentState.getAction()
    }
}

5.3.4 DATA STRUCTURES AND INTERFACES :資料結構與介面

The state machine relies on having states and transitions with a particular interface.
The state interface has the following form:

狀態機依賴於具有特定介面的狀態和轉換。
狀態介面具有以下形式:

class State
{
    def getAction();
    def getEntryAction();
    def getExitAction();
    
    def getTransitions();
}

Each of the getAction methods should return a list of actions to carry out. As we will see below, the getEntryAction is only called when the state is entered from a transition, and the getExitAction is only called when the state is exited. The rest of the time that the state is active, getAction is called. The getTransitions method should return a list of transitions that are outgoing from this state.

The transition interface has the following form:

每個getAction方法都應返回要執行的操作列表。 正如我們將在下面看到的,只有在從轉換進入狀態時才呼叫getEntryAction,並且僅在退出狀態時呼叫getExitAction。 其餘時間狀態為活動狀態,呼叫getAction。 getTransitions方法應返回從此狀態傳出的轉換列表。

轉換介面具有以下形式:

class Transition
{
  def isTriggered();
  def getTargetState();
  def getAction();
}

The isTriggered method returns true if the transition can fire; the getTarget-State method reports which state to transition to; and the getAction method returns a list of actions to carry out when the transition fires.

如果轉換可以觸發,則isTriggered方法返回true; getTarget-State方法報告要轉換到的狀態; 並且getAction方法返回轉換觸發時要執行的操作列表。

Transition Implementation :實現轉換

Only one implementation of the state class should be required: it can simply hold the three lists of actions and the list of transitions as data members, returning them in the corresponding get methods.

只需要一個狀態類的實現:它可以簡單地將三個動作列表和轉換列表儲存為資料成員,並在相應的get方法中返回它們。

In the same way, we can store the target state and a list of actions in the transition class and have its methods return the stored values. The isTriggered method is more difficult to generalize. Each transition will have its own set of conditions, and much of the power in this method is allowing the transition to implement any kind of tests it likes.

以同樣的方式,我們可以在轉換類中儲存目標狀態和操作列表,並使其方法返回儲存的值。 isTriggered方法更難以概括。 每個轉換都有自己的一組條件,這種方法的大部分功能是允許轉換實現它喜歡的任何型別的條件測試。

Because state machines are often defined in a data file and read into the game at run time, it is a common requirement to have a set of generic transitions. The state machine can then be set up from the data file by using the appropriate transitions for each state.

由於狀態機通常在資料檔案中定義並在執行時讀入遊戲,因此通常需要具有一組通用轉換。 然後,可以通過使用每個狀態的適當轉換從資料檔案中設定狀態機。

In the previous section on decision trees, we saw generic testing decisions that operated on basic data types. The same principle can be used with state machine transitions: we have generic transitions that trigger when data they are looking at is in a given range.

在上一節關於決策樹的部分中,我們看到了對基本資料型別進行操作的通用測試決策。 相同的原理可以與狀態機轉換一起使用:我們具有通用轉換,當它們正在檢視的資料處於給定範圍內時觸發。

Unlike decision trees, state machines don’t provide a simple way of combining these tests together to make more complex queries. If we need to transition based on the condition that the enemy is far away AND health is low, then we need some way of combining triggers together.

與決策樹不同,狀態機不提供將這些測試組合在一起以進行更復雜查詢的簡單方法。// 如果我們需要根據敵人在遠處且健康狀況低的條件進行過渡,那麼我們需要一些將觸發器組合在一起的方法。

In keeping with our polymorphic design for the state machine, we can accom- plish this with the addition of another interface: the condition interface. We can use a general transition class of the following form:

為了與狀態機的多型設計保持一致,我們可以通過新增另一個介面來實現這一點:條件介面。 我們可以使用以下形式的一般轉換類:

class Transition
{// 條件介面類
    actions
    def getAction(): return actions
  
    targetState
    def getTargetState(): return targetState
  
    condition
    def isTriggered(): return condition.test()
}

The isTriggered function now delegates the testing to its condition member.
Conditions have the following simple format:

isTriggered函式現在將測試委託給其條件成員。
條件具有以下簡單格式:

class Condition
{
   def test();
}

We can then make a set of sub-classes of condition for particular tests, just like we did for decision trees:

然後,我們可以為特定測試建立一組條件子類,就像我們為決策樹所做的那樣:

class FloatCondition (Condition)
{// 數值條件
    minValue;
    maxValue;
    testValue; // 我們想關注的遊戲資料
    def test()
    {
        return minValue <= testValue <= maxValue
    }
}

We can combine conditions together using Boolean sub-classes, such as AND, NOT, and OR:

我們可以使用布林子類將條件組合在一起,例如AND,NOT和OR:

class AndCondition (Condition)
{// 布林條件
    conditionA
    conditionB
    def test()
    {
        return conditionA.test() and conditionB.test();
    }
}

class NotCondition (Condition)
{
    condition
    def test()
    {
        return not condition.test();
    }
  
}

class OrCondition (Condition)
{
    conditionA
    conditionB
    def test()
    {
        return conditionA.test() or conditionB.test() 
    }
}

and so on, for any level of sophistication we need.

等等……,支援我們需要的任何複雜程度。

Weaknesses :缺點

This approach to transitions gives a lot of flexibility, but at the price of lots of method calls. In C++ these method calls have to be polymorphic, which can slow down the call and confuse the processor. All this adds time, which may make it unsuitable for use in every frame on lots of characters.

這種轉換方法提供了很大的靈活性,但代價是大量的方法呼叫。 在C ++中,這些方法呼叫必須是多型的,這會降低呼叫速度並使處理器混淆。 所有這些都增加了時間,這可能使其不適合在許多角色的每個幀中使用。

Several developers I have come across use a homegrown scripting language to ex- press conditions for transitions. This still allows designers to create the state machine rules, but can be slightly more efficient. In practice, however, the speed up over this approach is quite small, unless the scripting language includes some kind of compila- tion into machine code (i.e., Just In Time Compiling). For all but the simplest code, interpreting a script is at least as time-consuming as calling polymorphic functions.

我遇到的幾個開發人員使用自己開發的指令碼語言來表達轉換條件。 這仍然允許設計人員建立狀態機規則,但可以稍微提高效率。 然而,在實踐中,除非指令碼語言包括對機器程式碼的某種編譯(即,及時編譯),否則這種方法的速度非常快。 對於除最簡單程式碼之外的所有程式碼,解釋指令碼至少與呼叫多型函式一樣耗時。

5.3.6 PERFORMANCE :開銷

The state machine algorithm only requires memory to hold a triggered transition and the current state. It is O(1) in memory, and O(m) in time, where m is the number of transitions per state.

狀態機演算法僅需要儲存器來保持觸發轉換和當前狀態。 它在儲存器中是O(1),在時間上是O(m),其中m是每個狀態的轉換數。

The algorithm calls other functions in both the state and the transition classes, and in most cases the execution time of these functions accounts for most of the time spent in the algorithm.

該演算法呼叫狀態和轉換類中的其他函式,並且在大多數情況下,這些函式的執行時間佔演算法中花費的大部分時間。

5.3.7 IMPLEMENTATION NOTES :實現的一些說明

As I mentioned earlier, there are any number of ways to implement a state machine. The state machine described in this section is as flexible as possible. I’ve tried to aim for an implementation that allows you to experiment with any kind of state machine and add interesting features. In many cases it may be too flexible. If you’re only planning to use a small subset of its flexibility, then it is very likely to be unnecessarily inefficient.

正如我之前提到的,有許多方法可以實現狀態機。 本節中描述的狀態機儘可能靈活。 我試圖實現一個允許您嘗試任何型別的狀態機並新增有趣功能的實現。 在許多情況下,它可能過於靈活。 如果您只計劃使用其靈活性的一小部分,則很可能會產生不必要的低效率。

5.3.8 HARD-CODED FSM :FSM的硬編碼問題

A few years back, almost all state machines were hard-coded. The rules for transitions and the execution of actions were part of the game code. It has become less common as level designers get more control over building the state machine logic, but it is still an important approach.

幾年前,幾乎所有的狀態機都是硬編碼的。 轉換規則和動作的執行是遊戲程式碼的一部分。 隨著關卡設計師對構建狀態機邏輯的更多控制,它變得不那麼常見,但它仍然是一種重要的方法。

Pseudo-Code :虛擬碼

In a hard-coded FSM, the state machine consists of an enumerated value, indicating which state is currently occupied, and a function that checks if a transition should be followed. Here I’ve combined the two into a class definition (although I personally tend to associate hard-coded FSMs with developers still working in C).

在硬編碼的FSM中,狀態機由列舉值組成,指示當前佔用的狀態,以及檢查是否應遵循轉換的函式。 在這裡,我將兩者合併為一個類定義(儘管我個人傾向於將硬編碼的FSM與仍在C中工作的開發人員聯絡起來)。

class MyFSM
{
    // 宣告所有狀態名
    enum State
    {
        PATROL,
        DEFEND,
        SLEEP 
    };
    // 當前狀態
    myState;
    def update()
    {
        // 尋找當前狀態
        if myState == PATROL
        {
            if canSeePlayer()
                myState = DEFEND
            if tired()
                myState = SLEEP
        }
        else if myState == DEFEND
        {
            if not canSeePlayer()
                myState = PATROL 
        }
        else if myState == SLEEP
        {
            if not tired()
                myState = PATROL
        }  
        def notifyNoiseHeard(volume)
        {
            if myState == SLEEP and volume > 10:
            myState = DEFEND
        }
    }
}

Notice that this is pseudo-code for a particular state machine rather than a type of state machine. In the update function there is a block of code for each state. In that block of code the conditions for each transition are checked in turn, and the state is updated if required. The transitions in this example all call functions (tired and canSeePlayer), which I am assuming have access to the current game state.

請注意,這是特定狀態機的虛擬碼,而不是一種狀態機。 在更新功能中,每個狀態都有一個程式碼塊。 在該程式碼塊中,依次檢查每個轉換的條件,並在需要時更新狀態。 這個例子中的轉換都是呼叫函式(tired()和canSeePlayer()),我假設它們可以訪問當前的遊戲狀態。

In addition, I’ve added a state transition in a separate function, notifyNoiseHeard. I am assuming that the game code will call this function whenever the character hears a loud noise. This illustrates the difference between a polling (asking for informa- tion explicitly) and an event-based (waiting to be told information) approach to state transitions. Chapter 10 on world interfacing contains more details on this distinction.

另外,我在一個單獨的函式notifyNoiseHeard中添加了一個狀態轉換。 我假設只要角色聽到很大的噪音,遊戲程式碼就會呼叫此函式。 這說明了輪詢(明確要求資訊)和基於事件(等待被告知資訊)的狀態轉換方法之間的區別。 關於世界介面的第10章包含有關這種區別的更多細節。

The update function is called in each frame, as before, and the current state is used to generate an output action. To do this, the FSM might have a method containing conditional blocks of the following form:

更新函式像以前一樣在每個幀中呼叫,當前狀態用於生成輸出動作。 為此,FSM可能有一個包含以下形式的條件塊的方法:

def getAction()
{
    if myState == PATROL: return PatrolAction
    elif myState == DEFEND: return DefendAction
    elif myState == SLEEP: return SleepAction
}

Often, the state machine simply carries out the actions directly, rather than returning details of the action for another piece of code to execute.

通常,狀態機只是直接執行操作,而不是返回要執行的另一段程式碼的操作細節。

Performance :開銷

This approach requires no memory and is O(n + m), where n is the number of states, and m is the number of transitions per state.

這種方法不需要儲存器,並且是O(n + m),其中n是狀態數,m是每個狀態的轉換數。

Although this appears to perform worse than the flexible implementation, it is usually faster in practice for all but huge state machines (i.e., thousands of states).

雖然這似乎比靈活的實現更糟糕,但實際上除了巨大的狀態機(即數千個狀態)之外通常更快。

Weaknesses :缺點

Although hard-coded state machines are easy to write, they are notoriously difficult to maintain. State machines in games can often get fairly large, and this can appear as ugly and unclear code.

雖然硬編碼的狀態機很容易編寫,但它們很難維護。 遊戲中的狀態機通常會變得相當大,這可能看起來像醜陋和不清楚的程式碼。

Most developers, however, find that the main drawback is the need for program- mers to write the AI behaviors for each character. This implies a need to recompile the game each time the behavior changes. While it may not be a problem for a hobby game writer, it can become critical in a large game project that takes many minutes or hours to rebuild.

然而,大多數開發人員發現主要缺點是程式設計師需要為每個角色編寫AI行為。 這意味著每次行為改變時都需要重新編譯遊戲。 雖然它可能不是一個業餘愛好遊戲開發者的問題,但它可能在一個大型遊戲專案中變得至關重要,需要花費很多分鐘或幾小時來重建。

More complex structures, such as hierarchical state machines (see below), are also difficult to coordinate using hard-coded FSMs. With a more flexible implementation, debugging output can easily be added to all state machines, making it easier to track down problems in the AI.

更復雜的結構,例如分層狀態機(見下文),也很難使用硬編碼的FSM進行協調。 通過更靈活的實現,可以輕鬆地將除錯輸出新增到所有狀態機,從而更容易跟蹤AI中的問題。

5.3.9 HIERARCHICAL STATE MACHINES:分層狀態機

On its own, one state machine is a powerful tool, but it can be difficult to express some behaviors. One common source of difficulty is “alarm behaviors.”

就其本身而言,一臺狀態機是一種強大的工具,但表達某些行為可能很困難。一個常見的例子是“警報行為”。

Imagine a service robot that moves around a facility cleaning the floors. It has a state machine allowing it to do this. It might search around for objects that have been dropped, pick one up when it finds it, and carry it off to the trash compactor. This can be simply implemented using a normal state machine (see Figure 5.14).

想象一下一個機器人在清潔地板的設施周圍移動。 它有一個狀態機允許它這樣做。 它可能會搜尋掉落的物體,找到它時拾取一個物體,然後將它帶到垃圾壓縮機。 這可以使用普通狀態機簡單地實現(參見圖5.14)。

image-20181121110155364

Unfortunately, the robot can run low on power, whereupon it has to scurry off to the nearest electrical point and get recharged. Regardless of what it is doing at the time, it needs to stop, and when it is fully charged again, it needs to pick up where it left off. The recharging periods could allow the player to sneak by unnoticed, for example, or allow the player to disable all electricity to the area and thereby disable the robot.

不幸的是,機器人的能源不是無限的,因此它必須趕到最近的電氣點並進行充電。 不管它當時在做什麼,它需要停止。當它再次充滿電時,它需要從它停止的地方開始。 例如,充電時段可允許玩家不加註意地潛行,或允許玩家禁用該區域的所有電力,從而禁用機器人。(注:遊戲場景模擬)

This is an alarm mechanism: something that interrupts normal behavior to respond to something important. Representing this in a state machine leads to a dou- bling in the number of states.

這是一種警報機制:可以中斷正常行為以迴應重要事件。 在狀態機中表示這一點會導致狀態數量的增加。

With one level of alarm this isn’t a problem, but what would happen if we wanted the robot to hide when fighting breaks out in the corridor. If its hiding instinct is more important than its refuelling instinct, then it will have to interrupt refuelling to go hide. After the battle it will need to pick up refuelling where it left off, after which it will pick up whatever it was doing before that. For just 2 levels of alarm, we would have 16 states.

通過一層警報,這不是問題,但如果我們希望機器人在走廊中發生戰鬥時隱藏會發生什麼。 如果它的隱藏決策比加油決策優先順序更高,那麼就不得不中斷加油才能隱藏起來。 在戰鬥結束後,它需要在停止的地方接受加油,之後它將重拾它之前做的事情。 對於僅2層警報,我們將有16個狀態。(?)

Rather than combining all the logic into a single state machine, we can separate it into several. Each alarm mechanism has its own state machine, along with the original behavior. They are arranged in a hierarchy, so the next state machine down is only considered when the higher level state machine is not responding to its alarm.

我們可以將它分成幾個,而不是將所有邏輯組合到一個狀態機中。 每個報警機制都有自己的狀態機以及原始行為。 它們按層次結構排列,因此僅當較高級別的狀態機未響應其警報時才考慮下一個狀態機。

Figure 5.15 shows one alarm mechanism and corresponds exactly to the diagram above.

圖5.15顯示了一種報警機制,與上圖完全一致。

image-20181121111241030

We will nest one state machine inside another to indicate a hierarchical state ma- chine (Figure 5.16). The solid circle again represents the start state of the machine. When a composite state is first entered, the circle with H* inside it indicates which sub-state should be entered.

我們將一個狀態機巢狀在另一個狀態機中以指示分層狀態機(圖5.16)。 實心圓圈再次表示機器的啟動狀態。 首次輸入複合狀態時,其中帶有H *的圓圈表示應輸入哪個子狀態。

image-20181121111547584

If the composite state has already been entered, then the previous sub-state is returned to. The H* node is called the “history state” for this reason.

如果已經輸入複合狀態,則返回先前的子狀態。 由於這個原因,H *節點被稱為“歷史狀態”。

The details of why there’s an asterisk after the H, and some of the other vagaries of the UML state chart diagram, are beyond the scope of this chapter. Refer back to Pilone [2005] for more details.

H之後有一個星號的原因以及UML狀態圖表中的一些其他變幻莫測的細節超出了本章的範圍。 有關更多詳細資訊,請參閱Pilone [2005]。

Rather than having separate states to keep track of the non-alarm state, we intro- duce nested states. We still keep track of the state of the cleaning state machine, even if we are in the process of refuelling. When the refuelling is over, the cleaning state machine will pick up where it left off.

我們引入巢狀狀態,而不是使用單獨的狀態來跟蹤非警報狀態。 即使我們正在加油,我們仍然會跟蹤清潔狀態機的狀態。 當加油結束時,清潔狀態機將從停止的地方開始。

In effect, we are in more than one state at once: we might be in the “Refuel” state in the alarm mechanism, while at the same time be in the “Pick Up Object” state in the cleaning machine. Because there is a strict hierarchy, there is never any confusion about which state wins out: the highest state in the hierarchy is always in control.

實際上,我們同時處於多個狀態:我們可能處於報警機制中的“加油”狀態,同時處於清洗機制中的“拾取物件”狀態。 因為存在嚴格的層次結構,所以對於執行哪個狀態並不存在任何混淆:層次結構中的最高狀態始終處於控制之中。

To implement this, we could simply arrange the state machines in our program so that one state machine calls another if it needs to. So if the refuelling state ma- chine is in its “Clean Up” state, it calls the cleaning state machine and asks it for the action to take. When it is in the “Refuel” state, it returns the refuelling action directly.

為了實現這一點,我們可以簡單地在我們的程式中安排狀態機,以便一個狀態機在需要時呼叫另一個狀態機。 因此,如果加油狀態機處於“清理”狀態,它會呼叫清潔狀態機並要求其採取措施。 當它處於“加油”狀態時,它直接返回加油動作。

While this would lead to slightly ugly code, it would implement our scenario. Most hierarchical state machines, however, support transitions between levels of the hierarchy, and for that we’ll need more complex algorithms.

雖然這會導致稍微醜陋的程式碼,但它會實現我們的場景。 但是,大多數分層狀態機支援層次結構級別之間的轉換,為此我們需要更復雜的演算法。

For example, let’s expand our robot so that it can do something useful if there are no objects to collect. It makes sense that it will use the opportunity to go and recharge, rather than standing around waiting for its battery to go flat. The new state machine is shown in Figure 5.17.

例如,讓我們擴充套件我們的機器人,以便在沒有要收集的物件時它可以做一些有用的事情。 這是有意義的,它會利用這個機會去充電,而不是站在那裡等待電池電量消失。 新的狀態機如圖5.17所示。

image-20181121112706544

Notice that we’ve added one more transition: from the “Search” state right out into the “Refuel” state. This transition is triggered when there are no objects to collect. Because we transitioned directly out of this state, the inner state machine no longer has any state. When the robot has refuelled and the alarm system transitions back to cleaning, the robot will not have a record of where to pick up from, so it must start the state machine again from its initial node (“Search”).

請注意,我們又添加了一個轉換:從“搜尋”狀態直接進入“加油”狀態。 沒有要收集的物件時會觸發此轉換。 因為我們直接從這個狀態轉換,內部狀態機不再具有任何狀態。 當機器人加滿了油並且報警系統轉換回清潔時,機器人將無法記錄從哪裡取貨,因此它必須從其初始節點(“搜尋”)再次啟動狀態機。

The Problem :問題

We’d like an implementation of a state machine system that supports hierarchical state machines. We’d also like transitions that pass between different layers of the machine.

我們想要一個支援分層狀態機的狀態機系統的實現。 我們也喜歡在機器的不同層之間傳遞的過渡。

The Algorithm :演算法

In a hierarchical state machine each state can be a complete state machine in its own right. We therefore rely on recursive algorithms to process the whole hierarchy. As with most recursive algorithms, this can be pretty tricky to follow. The simplest im- plementation covered here is doubly tricky because it recurses up and down the hier- archy at different points. I’d encourage you to use the informal discussion and exam- ples in this section alongside the pseudo-code in the next section and play with the Hierarchical State Machine program on the CD to get a feel for how it is all working.

在分層狀態機中,每個狀態本身可以是完整的狀態機。 因此,我們依靠遞迴演算法來處理整個層次結構。 與大多數遞迴演算法一樣,這可能非常棘手。 這裡涉及的最簡單的實現是雙重棘手的,因為它在不同的點上上下起伏。 我鼓勵您使用本節中的非正式討論和示例以及下一節中的虛擬碼,並使用CD上的Hierarchical State Machine程式來了解它是如何工作的。

The first part of the system returns the current state. The result is a list of states, from highest to lowest in the hierarchy. The state machine asks its current state to return its hierarchy. If the state is a terminal state, it returns itself; otherwise, it returns itself and adds to it the hierarchy of state from its own current state.

系統的第一部分返回當前狀態。 結果是狀態列表,從層次結構的最高到最低。 狀態機要求其當前狀態返回其層次結構。 如果狀態是終端狀態,則返回自身; 否則,它返回自身並從其當前狀態向其新增狀態層次結構。

In Figure 5.18 the current state is [State L, State A].

在圖5.18中,當前狀態是[狀態L,狀態A]。

image-20181121113748202

The second part of the hierarchical state machine is its update. In the original state machine we assumed that each state machine started off in its initial state. Because the state machine always transitioned from one state to another, there was never any need to check if there was no state. State machines in a hierarchy can be in no state; they may have a cross hierarchy transition. The first stage of the update, then, is to check if the state machine has a state. If not, it should enter its initial state.

分層狀態機的第二部分是它的更新。 在原始狀態機中,我們假設每個狀態機在其初始狀態下啟動。 因為狀態機總是從一個狀態轉換到另一個狀態,所以從來沒有必要檢查是否沒有狀態。 層次結構中的狀態機可以處於無狀態; 他們可能有一個跨層次的過渡。 然後,更新的第一階段是檢查狀態機是否具有狀態。 如果沒有,它應該進入其初始狀態。

Next, we check if the current state has a transition it wants to execute. Transitions at higher levels in the hierarchy always take priority, and the transitions of sub-states will not be considered if the super-state has one that triggers.

接下來,我們檢查當前狀態是否有要執行的轉換。 層次結構中較高級別的轉換始終具有優先順序,如果父狀態已經被觸發,則不會考慮子狀態的轉換。

A triggered transition may be one of three types: it might be a transition to an- other state at the current level of the hierarchy; it might be a transition to a state higher up in the hierarchy; or it might be a transition to a state lower in the hierarchy. Clearly, the transition needs to provide more data than just a target state. We allow it to return a relative level: how many steps up or down the hierarchy the target state is.

觸發轉換可以是以下三種類型之一:它可能是在層次結構的當前級別轉換到另一種狀態; 它可能是向層次結構中更高級別的狀態過渡; 或者它可能是轉換到層次結構中較低的狀態。 顯然,轉換需要提供的資料多於目標狀態。 我們允許它返回一個相對級別:目標狀態在層次結構中向上或向下的步數。

We could simply search the hierarchy for the target state and not require an ex- plicit level. While this would be more flexible (we wouldn’t have to worry about the level values being wrong), it would be considerably more time-consuming. A hybrid, but fully automatic, extension could search the hierarchy once offline and store all appropriate level values.

我們可以簡單地在層次結構中搜索目標狀態,而不需要一個明確的級別。 雖然這會更靈活(我們不必擔心水平值是錯誤的),但它會更加耗時。 混合但全自動的擴充套件可以在離線時搜尋層次結構並存儲所有適當的級別值。

So the triggered transition has a level of zero (state is at the same level), a level greater than zero (state is higher in the hierarchy), or a level less than zero (state is lower in the hierarchy). It acts differently depending on which category the level falls into.

因此,觸發轉換擁有三種情況:級別為零(狀態處於同一級別),級別大於零(層次結構中的狀態較高),或小於零的級別(狀態在層次結構中較低)。 它的行為取決於級別所屬的類別。

If the level is zero, then the transition is a normal state machine transition and can be performed at the current level, using the same algorithm used in the finite state machine.

如果級別為零,則轉換是正常狀態機轉換,並且可以使用在有限狀態機中使用的相同演算法在當前級別執行。

If the level is greater than zero, then the current state needs to be exited and noth- ing else needs to be done at this level. The exit action is returned, along with an indication to whoever called the update function that the transition hasn’t been com- pleted. We will return the exit action, the transition outstanding, and the number of levels higher to pass the transition. This level value is decreased by one as it is returned. As we will see, the update function will be returning to the next highest state machine in the hierarchy.

如果級別大於零,則需要退出當前狀態,而不需要在此級別完成其他操作。 返回退出操作,同時向任何呼叫Update()的位置指示轉換尚未完成。 我們將返回退出時執行的行為、未完成的轉換以及更高級別的數量以通過轉換。 該級別值在返回時減少一。 正如我們將看到的,更新功能將返回到層次結構中的下一個最高狀態機。

If the level is less than zero, then the current state needs to transition to the ancestor of the target state on the current level in the hierarchy. In addition, each of the children of that state also needs to do the same, down to the level of the final desti- nation state. To achieve this we use a separate function, updateDown, that recursively performs this transition from the level of the target state back up to the current level and returns any exit and entry actions along the way. The transition is then complete and doesn’t need to be passed on up. All the accumulated actions can be returned.

// 如果級別小於零,則當前狀態需要轉換到層次結構中當前級別上的目標狀態的父狀態。 此外,該狀態的每個孩子也需要做同樣的事情,直到最終目標狀態的水平。 為了實現這一點,我們使用一個單獨的函式updateDown,它遞迴地執行從目標狀態級別到當前級別的轉換,並返回任何退出和進入操作。 然後轉換完成,不需要向上傳遞。 可以返回所有累積的動作。

So we’ve covered all possibilities if the current state has a transition that triggers. If it does not have a transition that triggers, then its action depends on whether the current state is a state machine itself. If not, and if the current state is a plain state, then we can return the actions associated with being in that state, just as before.

因此,如果當前狀態具有觸發的轉換,我們已經涵蓋了所有可能性。 如果它沒有觸發的轉換,那麼它的動作取決於當前狀態是否是狀態機本身。 如果不是,並且如果當前狀態是普通狀態,那麼我們可以像以前一樣返回與處於該狀態相關聯的動作。

If the current state is a state machine, then we need to give it the opportunity to trigger any transitions. We can do this by calling its update function. The update func- tion will handle any triggers and transitions automatically. As we saw above, a lower level transition that fires may have its target state at a higher level. The update func- tion will return a list of actions, but it may also return a transition that it is passing up the hierarchy and that hasn’t yet been fired.

如果當前狀態是狀態機,那麼我們需要給它機會來觸發任何轉換。 我們可以通過呼叫它的更新函式來實現。 更新功能將自動處理任何觸發和轉換。 如上所述,觸發的較低級別轉換可能使其目標狀態處於較高級別。 更新功能將返回一個操作列表,但它也可能返回一個它正在向層次結構傳遞但尚未觸發的轉換。

If such a transition is received, its level is checked. If the level is zero, then the transition should be acted on at this level. The transition is honored, just as if it were a regular transition for the current state. If the level is still greater than zero (it should never be less than zero, because we are passing up the hierarchy at this point), then the state machine should keep passing it up. It does this, as before, by exiting the current state and returning the following pieces of information: the exit action; any actions provided by the current state’s update function; the transition that is still pending; and the transition’s level, less one.

如果收到這樣的轉換,則檢查其級別。 如果級別為零,則應在此級別上執行轉換。轉換已經執行,就像它是當前狀態的常規過渡一樣。 如果級別仍然大於零(它應該永遠不會小於零,因為我們此時正在調整層次結構),那麼狀態機應該繼續傳遞它。 它像以前一樣通過退出當前狀態並返回以下資訊來執行此操作:退出操作; 當前狀態更新功能提供的任何行動; 仍未決的狀態轉換; 和轉換的層級//,少一個。

If no transition is returned from the current state’s update function, then we can simply return its list of actions. If we are at the top level of the hierarchy, the list alone is fine. If we are lower down, then we are also within a state, so we need to add the action for the state we’re in to the list we return.

如果沒有從當前狀態的更新函式返回轉換,那麼我們可以簡單地返回其動作列表。 如果我們處於層次結構的頂層,那麼單獨列表就可以了。 如果我們處於較低的層級,那麼我們也處於一個狀態,所以我們需要為我們返回的列表中的狀態新增動作。

Fortunately, this algorithm is at least as difficult to explain as it is to implement. To see how and why it works, let’s work through an example.

幸運的是,這種演算法至少與實現一樣難以解釋。 為了瞭解它的工作原理和原因,讓我們來看一個例子。

Examples :例子

Figure 5.19 shows a hierarchical state machine that we will use as an example.

圖5.19顯示了我們將用作示例的分層狀態機。

image-20181121133703875

To clarify the actions returned for each example, we will say S-entry is the set of entry actions for state S, similarly S-active and S-exit for active and exit actions. In transitions we use the same format 1-actions for the actions associated with transi- tion 1.

為了闡明每個示例返回的操作,我們將說S-entry是狀態S的入口操作集,類似於活動和退出操作的S-active和S-exit。 在轉換中,我們對與轉換1相關的操作使用相同的格式1動作。

These examples can appear confusing if you skim them through. If you’re having trouble with the algorithm, I urge you to follow through step by step with both the diagram above and the pseudo-code from the next section.

如果您瀏覽它們,這些示例可能會讓您感到困惑。 如果您在使用演算法時遇到問題,我建議您按照上圖和下一部分的虛擬碼一步一步地進行操作。

Suppose we start just in State L, and no transition triggers. We will transition into State [L, A], because L’s initial state is A. The update function will return: L-active and A-entry, because we are staying in L and just entering A.

假設我們只是在狀態L開始,沒有轉換觸發器。 我們將轉換到狀態[L,A],因為L'的初始狀態是A.更新函式將返回:L-active和A-entry,因為我們留在L並且只是進入A.

Now suppose transition 1 is the only one that triggers. The top-level state ma- chine will detect no valid transitions, so will call state machine L to see if it has any. L finds that its current state (A) has a triggered transition. Transition 1 is a transition at the current level, so it is handled within L and not passed anywhere. A transitions to A, and L’s update function returns: A-exit, 1-actions, B-entry. The top-level state machine accepts these actions and adds its own active action. Because we have stayed in State L throughout, the final set of actions is A-exit, 1-actions, B-entry, L-active. The current state is [L, B].

現在假設轉換1是唯一觸發的轉換。 頂層狀態機將檢測到無有效轉換,因此將呼叫狀態機L以檢視它是否有任何轉換。 L發現其當前狀態(A)具有觸發轉換。 轉換1是當前級別的轉換,因此它在L內處理,不會在任何地方傳遞。 轉換到A,並且L的更新函式返回:A-exit,1--actions,B-entry。 頂層狀態機接受這些操作並新增其自己的活動操作。 因為我們始終處於狀態L,所以最後一組動作是A-exit,1-actions,B-entry,L-active。 當前狀態是[L,B]。

From this state, transition 4 triggers. The top-level state machine sees that transi- tion 4 triggers, and because it is a top-level transition, it can be honored immediately. The transition leads to State M, and the corresponding actions are L-exit, 4-actions, M-entry. The current state is [M]. Note that L is still keeping a record of being in State B, but because the top-level state machine is in State M, this record isn’t used at the moment.

從這個狀態,轉換4觸發。 頂級狀態機看到轉換4觸發,並且因為它是頂級轉換,所以它可以立即兌現。 轉換導致狀態M,並且相應的動作是L-exit,4-actions,M-entry。 當前狀態是[M]。 請注意,L仍然保留在狀態B中的記錄,但由於頂級狀態機處於狀態M,因此此記錄不會被使用。

We’ll go from State M to State N in the normal way through transition 5. The pro- cedure is exactly the same as for the previous example and the non-hierarchical state machine. Now transition 6 triggers. Because it is a level zero transition, the top-level state machine can honor it immediately. It transitions into State L and returns the ac- tions N-exit, 6-actions, L-entry. But now, L’s record of being in State B is important: we end up in State [L, B] again. In our implementation we don’t return the B-entry action, because we didn’t return the B-exit action when we left State L previously. This is a personal preference on my part and isn’t fixed in stone. If you want to exit and re-enter State B, then you can modify your algorithm to return these extra actions at the appropriate time.

我們將通過轉換5以正常方式從狀態M轉到狀態N.該過程與前一個示例和非分層狀態機完全相同。 現在轉換6個觸發器。 因為它是一個零級轉換,所以頂級狀態機可以立即兌現它。 它轉換到狀態L並返回N-exit,6-actions,L-entry的動作。 但是現在,L在狀態B的記錄很重要:我們再次進入狀態[L,B]。 在我們的實現中,我們不返回B-entry動作,因為我們之前離開狀態L時沒有返回B-exit動作。 這是我個人的偏好,並不是固定的。 如果要退出並重新輸入狀態B,則可以修改演算法以在適當的時間返回這些額外的操作。

Now suppose from State [L, B] transition 3 triggers. The top-level state machine finds no triggers, so it will call state machine L to see if it has any. L finds that State B has a triggered transition. This transition has a level of one: its target is one level higher in the hierarchy. This means that State B is being exited, and it means that we can’t honor the transition at this level. We return B-exit, along with the uncompleted transition, and the level minus one (i.e., zero, indicating that the next level up needs to handle the transition). So control returns to the top-level update function. It sees that L returned an outstanding transition, with zero level, so it honors it, transitioning in the normal way to State N. It combines the actions that L returned (namely,B-exit) with the normal transition actions to give a final set of actions: B-exit, L-exit,3-actions, N-entry. Note that, unlike in our third example, L is no longer keeping track of the fact that it is in State B, because we transitioned out of that state. If we fire transition 6 to return to State L, then State L’s initial state, A, would be entered, just like in the first example.

現在假設從狀態[L,B]過渡3觸發。頂級狀態機找不到觸發器,因此它將呼叫狀態機L以檢視它是否有任何觸發器。 L發現狀態B已經觸發轉換。此轉換的級別為1:其目標在層次結構中高一級。這意味著狀態B正在退出,並且我們無法履行這一級別的過渡。我們返回B-exit,以及未完成的轉換,以及級別減1(即,零,表示下一級需要處理轉換)。因此控制移交回上層的Update函式。Update函式得到了一個未完成的狀態轉換,零級別,所以Update函式將執行這個轉換:以正常方式轉換到狀態N。它將L返回的動作(即B-exit)與正常的過渡動作結合起來給出一組最終動作集:B-exit,L-exit,3-actions,N-entry。請注意,與我們的第三個示例不同,L不再跟蹤它在狀態B中執行的事,因為我們已經轉換出該狀態。如果我們觸發轉換6返回到狀態L,則將輸入State L'的初始狀態A,就像在第一個示例中一樣。

Our final example covers transitions with level less than zero. Suppose we moved from State N to State M via transition 7. Now we make transition 2 trigger. The top- level state machine looks at its current state (M) and finds transition 2 triggered. It has a level of minus one, because it is descending one level in the hierarchy. Because it has a level of minus one, the state machine calls the updateDown function to perform the recursive transition. The updateDown function starts at the state machine (L) that contains the final target state (C), asking it to perform the transition at its level. State machine L, in turn, asks the top-level state machine to perform the transition at its level. The top-level state machine changes from State M to State L, returning M-exit, L-entry as the appropriate actions. Control returns to state machine L’s updateDown function. State machine L checks if it is currently in any state (it isn’t, since we left State B in the last example). It adds its action, C-entry, to those returned by the top- level machine. Control then returns to the top-level state machine’s update function: the descending transition has been honored, it adds the transition’s actions to the result, and returns M-exit, C-entry, L-entry, 2-actions.

我們的最後一個示例涵蓋了級別小於零的轉換。假設我們通過轉換7從狀態N移動到狀態M,現在我們通過觸發器2執行轉換。頂級狀態機檢視其當前狀態(M)並找到觸發的轉換2。它的級別為-1,因為它在層次結構中下降一級。因為它的級別為-1,所以狀態機呼叫updateDown函式來執行遞迴轉換。 updateDown函式從包含最終目標狀態(C)的狀態機(L)開始,要求它在其級別執行轉換。狀態機L反過來要求頂級狀態機在其級別執行轉換。頂級狀態機從狀態M變為狀態L,返回M-exit,L-entry作為適當的動作。控制返回狀態機L的updateDown函式。狀態機L檢查它當前是否處於任何狀態(它不是,因為我們在最後一個例子中離開了狀態B)。它將其操作C-entry新增到頂級計算機返回的操作中。然後,Control返回到頂級狀態機的Update函式:已經執行過降序轉換,它將轉換的動作新增到結果中,並返回M-exit,C-entry,L-entry,2-actions。

If state machine L had still been in State B, then when L’s updateDown function was called, it would transition out of B and into C. It would add B-exit and C-entry to the actions that it received from the top-level state machine.

如果狀態機L仍處於狀態B,那麼當呼叫L的 updateDown函式時,它將從B轉換為C。它會將B-exit和C-entry新增到它從頂層狀態機接收的操作集中。

Pseudo-Code :虛擬碼

The hierarchical state machine implementation is made up of five classes and forms one of the longest algorithms in this book. The State and Transition classes are similar to those in the regular finite state machine. The HierarchicalStateMachine class runs state transitions, and SubMachineState combines the functionality of the state machine and a state. It is used for state machines that aren’t at the top level of the hierarchy. All classes but the Transition inherit from a HSMBase class, which simpli- fies the algorithm by allowing functions to treat anything in the hierarchy in the same way.

分層狀態機實現由五個類組成,並形成本書中最長的演算法之一。 State和Transition類與常規有限狀態機中的類相似。 HierarchicalStateMachine類執行狀態轉換,SubMachineState結合狀態機和狀態的功能。 它用於不在層次結構頂層的狀態機。 除了Transition之外的所有類都繼承自HSMBase類,它通過允許函式以相同的方式處理層次結構中的任何內容來簡化演算法。

The HSMBase has the following form:

HSMBase類如下所示:

class HSMBase
    # The structure returned by update
    // Update函式返回結果的結構體
    struct UpdateResult
      actions   // 行為
      transition// 轉換路徑
      level     // 層級
    def getAction(): return []
    def update()
    {
        UpdateResult result
        result.actions = getAction()
        result.transition = None
        result.level = 0
        
        return result        
    }
    // 未實現的功能?
    def getStates() # unimplemented function

The HierarchicalStateMachine class has the following implementation:

HierarchicalStateMachine類有著如下實現:

class HierarchicalStateMachine (HSMBase)
    # List of states at this level of the hierarchy
    // 本層次的狀態列表
    states

    # The initial state for when the machine has to
    # current state.
    // 機器必須處於當前狀態的初始狀態
    initialState
    
    # The current state of the machine.
    // 狀態機的當前狀態
    currentState = initialState
    
    # Gets the current state stack
    // 獲得當前狀態棧
    def getStates():
      if currentState: return currentState.getStates()
      else: return []
      
    # Recursively updates the machine.
    // 遞迴更新狀態機
    def update():
        # If we’re in no state, use the initial state
        // 如果沒有處於任何狀態,使用初始狀態
        if not currentState:
            currentState = initialState
            return currentState.getEntryAction()
        
        # Try to find a transition in the current state
        // 從當前狀態嘗試找到一個轉換
        triggeredTransition = None
        for transition in currentState.getTransitions():
            if transition.isTriggered():
            triggeredTransition = transition
            break
        
        # If we’ve found one, make a result structure for it
        // 如果我們找到了一個轉換,為其生成一個結果結構體
        if triggeredTransition:
            result = UpdateResult()
            result.actions = []
            result.transition = triggeredTransition
            result.level = triggeredTransition.getLevel()
        
        # Otherwise recurse down for a result
        // 否則遞迴獲得結果
        else:
            result = currentState.update()
        
        # Check if the result contains a transition
        // 檢查結果中是否包含轉換
        if result.transition:

            # Act based on its level
            // 基於本層執行
            if result.level == 0:
                # Its on our level: honor it
                // 結果行為在本層:執行
                targetState = result.tra