Game !
C++

[BT] 요약2023. 8. 18. 20:48

예시 1

 

예시 2
예시 3

 

 

각 노드들은 자신의 상태를 반환해야 합니다

 

Selector는 자식 노드를 실행하여 이 중 하나라도 True를 리턴하면 True를 리턴

> Selector Node는 자식 노드 중에서 처음으로 Success 나 Running 상태를 가진 노드가 발생하면 그 노드까지 진행하고 멈춥니다.
그러므로 Evaluate() 메소드 구현은 아래와 같습니다.
1. 자식 상태: Running일 때 -> Running 반환
2. 자식 상태: Success 일 때 -> Success 반환
3. 자식 상태: Failure일 때 -> 다음 자식으로 이동

 

Sequence는 모든 자식 노드가 True를 리턴할 때 True를 리턴 (Sequence는 자식 중 하나라도 False면 False를 리턴)

> Sequence Node는 자식 노드를 왼쪽에서 오른쪽으로 진행하면서 Failure 상태가 나올 때까지 진행하게 됩니다.
그러므로 Evaluate() 메소드 구현은 아래와 같습니다.
1. 자식 상태: Running 일 때 -> Running 반환
2. 자식 상태: Success 일 때 -> 다음 자식으로 이동
3. 자식 상태: Failure 일 때 -> Failure 반환

 

Sequence Node 의 주의점

Running 상태일 때는 그 상태를 계속 유지해야 하기 때문에

다음 자식 노드로 이동하면 안 되고, 다음 프레임 때도 그 자식에 대한 평가를 진행해야 합니다.

예를 들어, Sequence Node에 적 발견(Detect), 이동(Move), 공격(Attack) 총 3개의 자식 노드가 있다고 가정해 보겠습니다.

( 프레임마다 노드에 진입하는 상황은 "N차"로 가정하였습니다.)

1차 : 적을 발견하고 적을 향해 이동해야한다.

2차 : 발견한 적을 향해 아직 이동 중이다.

3차 : 발견한 적을 향해 아직 이동 중이다.

4차 : 이동이 완료되어 적을 공격한다.

 

만약 이때 running에서 반환되지 않고 다음 자식 노드로 이동하게 되면 어떻게 될까요?
당연히 아직 적에게 다가가지도 못했는데 적을 향해 공격하게 됩니다.
그러므로, Running 상태에서는 Success와 다르게 다음 자식으로 이동하지 않고 Running을 반환해 줘서 다음 진입 시에도 Running 상태를 유지할 수 있도록 해주어야 합니다.

 

 

 

 

유의할 점은 노드는 추가된 순서에 따라 차례대로 호출 되기때문에 노드 순서가 캐릭터의 행동에 영향을 끼치게 됩니다.

 

 

오크 사냥 행동 트리가 작동 되는 동작 순서

 

Root에 Selector를 먼저 추가하였기 때문에 실행되면 Selector의 노드를 먼저 탐하게 됩니다. Selector에는 오크를 찾아 공격하는 시퀀스와 플레이어를 이동하는 시퀀스 2개가 추가되어있습니다.

 

먼저 첫번째 시퀀스인 오크를 찾아 공격하는 시퀀스를 탐색하게됩니다. CheckIsHereOrc 노드를 통해 오크가 현 위치에 있는 경우 True가 리턴되기 때문에 다음 노드인 AttackOrc 노드가 실행됩니다. 만약 오크가 없다면 False가 리턴 되기 때문에 다음 노드를 실행하지 않고, 현재 시퀀스 노드도 False를 리턴 합니다.
 
첫 번째 시퀀스가 False가 되면, Selector는 다음 시퀀스 노드를 탐색하게 됩니다. 두 번째 시퀀스에는 PlayerMove 만 있습니다. 이는 플레이어 이동 후 True를 리턴하게됩니다. Selector는 두 번째 시퀀스에서 True를 리턴 받았기 때문에 True를 리턴해줍니다.
 
Root는 이제 다음 노드인 IsPlayerDead를 실행합니다. 플레이어가 죽었다면 True, 아직 안죽었으면 False를 리턴합니다.
 
이렇게 노드들을 탐색하면서 Root 노드가 True를 리턴하게 되면 (IsPlayerDead가 True면) 프로그램을 종료하게 됩니다.

 

▼ 코드

더보기
1
2
3
4
5
6
7
8
9
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
class Node
{
public:
    virtual bool Invoke() = 0;
};
 
class CompositeNode : public Node
{
public:
    void AddChild(Node *node)
    {
        mChildren.emplace_back(node);
    }
    const std::list<Node*>& GetChildren()
    {
        return mChildren;
    }
private:
    std::list<Node*> mChildren;
};
 
class Selector : public CompositeNode
{
public:
    virtual bool Invoke() override
    {
        for (auto node : GetChildren())
        {
            if (node->Invoke())
                return true;
        }
        return false;
    }
};
 
class Sequence : public CompositeNode
{
public:
    virtual bool Invoke() override
    {
        for (auto node : GetChildren())
        {
            if (!node->Invoke())
                return false;
        }
        return true;
    }
};
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
BT::Sequence *root = new BT::Sequence(); // 루트 노드(시퀀스 노드로 생성)
BT::Selector *selector = new BT::Selector(); // 셀렉터
BT::Sequence *seqOrcKill = new BT::Sequence(); // 오크가 있으면 오크를 공격하는 시퀀스
BT::Sequence *seqMove = new BT::Sequence(); // 플레이어 이동 시퀀스
 
BT::Node *playerIsDead = new PlayerIsDead(); // 플레이어가 죽었는지 체크
BT::Node *checkIsHereOrc = new CheckIsHereOrc(); // 현 위치에 오크가 있는지 체크
BT::Node *attackOrc = new AttackOrc(); // 오크 공격 액션
BT::Node *playerMove = new PlayerMove(); // 플레이어 이동 액션
 
root->AddChild(selector);
root->AddChild(playerIsDead);
 
selector->AddChild(seqOrcKill);
selector->AddChild(seqMove);
 
seqOrcKill->AddChild(checkIsHereOrc);
seqOrcKill->AddChild(attackOrc);
 
seqMove->AddChild(playerMove);
 
while (!root->Invoke())
{
    std::cout << "--------------------------------------" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
cs

 

 

< 스스로 정리 >

셀렉터 노드 : 자식 list 를 가진다. 순회하며 자식의 invoke 를 호출한다. 상태를 받아서 반환한다.
시퀀서 노드 : 자식 list 를 가진다. 순회하며 식의 invoke 를 호출한다. 상태를 받아서 반환한다.
액터 노드 : update 함수를 가진다. update  안에 있는 invoke 를 호출하여 러닝, 성공, 실패 상태를 반환한다.

매 틱하다 BT 루트부터 시작하여 Update를 호출해서 진행한다.

 

'C++' 카테고리의 다른 글

[C++] 함수 객체와 임시 객체  (0) 2023.11.23
[펌] 생성자 소멸자 관련  (0) 2023.08.19
[C++] C++ 스타일 파일입출력  (0) 2022.11.12
[C++] 코드 위치나 정보를 받아오는 매크로 들  (0) 2022.05.08
[C++] interface  (0) 2022.05.06