W298.dev
ProjectsPostsAbout Me
youtube
Pages
ProjectsPostsAbout Me
Posts
TAGS
All
RL-Obstacle-Avoid
RL-Competitive
Robot-Escape
Assembly Definition
ML-Agent
RL-Obstacle-Avoid
RL-Competitive
Unity
RL-Obstacle-Avoid
RL-Competitive
Robot-Escape
Assembly Definition
SERIES
RL-Obstacle-Avoid
RL-Competitive
Robot-Escape

AI Behavior Tree (4)

https://velog.velcdn.com/images/lutca1320/post/b8315ebc-92fc-4871-9e69-505295b5d70b/image.png
마지막으로 Cover 기능을 구현할 것이다.

Cover

1public class Cover : Node
2{
3    private EnemyRobotBT ebt;
4
5    public Cover(BehaviorTree bt) : base(bt)
6    {
7        ebt = (EnemyRobotBT)bt;
8    }
9
10    public override NodeState Evaluate()
11    {
12        if (!ebt.ai.closestCoverPoint)
13        {
14            float minDistance = 10000;
15            foreach (var coverPoint in GameObject.FindGameObjectsWithTag("CoverPoint"))
16            {
17                float dPointToAI = Vector3.Distance(coverPoint.transform.position, ebt.ai.transform.position);
18                float dEnemyToPoint = ebt.ai.enemyObject ? Vector3.Distance(ebt.ai.enemyObject.transform.position, coverPoint.transform.position) : 1;
19
20                bool isBlock = !ebt.ai.enemyObject || Physics.Linecast(coverPoint.transform.position, ebt.ai.enemyObject.transform.position, 1 << LayerMask.NameToLayer("Obstacle"));
21
22                float value = ebt.ai.enemyObject ? (1 / dEnemyToPoint) : dPointToAI;
23                if (value < minDistance && isBlock)
24                {
25                    ebt.ai.closestCoverPoint = coverPoint;
26                    minDistance = value;
27                }
28            }
29        }
30
31        if (!ebt.ai.closestCoverPoint) return NodeState.FAILURE;
32
33        ebt.ai.StartMove(ebt.ai.closestCoverPoint.transform.position);
34
35        if (Vector3.Distance(ebt.ai.navAgent.transform.position, ebt.ai.closestCoverPoint.transform.position) <= ebt.ai.navAgent.stoppingDistance)
36        {
37            Quaternion toRotation = ebt.ai.closestCoverPoint.transform.rotation;
38            ebt.ai.transform.rotation = Quaternion.RotateTowards(ebt.ai.transform.rotation, toRotation, Time.deltaTime * 500);
39
40            ebt.ai.inputHandler.isCrouch = true;
41        }
42
43        return NodeState.RUNNING;
44    }
45}
46
https://velog.velcdn.com/images/lutca1320/post/41fbdcf9-c718-40df-9add-4765ce0bb73a/image.webp
일정 수치 아래로 체력이 내려가면 주변에 있는 CoverPoint 로 이동하여 Cover 한다. 일정 시간이 지나 체력이 회복되면 Cover 를 종료한다.

IsHealthLow

1public class IsHealthLow : Node
2{
3    private EnemyRobotBT ebt;
4    private float threshold;
5
6    public IsHealthLow(BehaviorTree bt, float threshold) : base(bt)
7    {
8        ebt = (EnemyRobotBT)bt;
9        this.threshold = threshold;
10    }
11
12    public override NodeState Evaluate()
13    {
14        return (ebt.ai.statusController.health <= threshold) ? NodeState.SUCCESS : NodeState.FAILURE;
15    }
16}
17
https://velog.velcdn.com/images/lutca1320/post/8746309f-b606-42b4-a34d-8251a152877a/image.webp
체력이 상당히 낮을 경우, Cover 하지 않고 맞서 싸운다.

Behavior Tree 조립

1public class EnemyRobotBT : BehaviorTree
2{
3    [NonSerialized]
4    public EnemyRobotAI ai;
5
6    private void Awake()
7    {
8        ai = GetComponent<EnemyRobotAI>();
9    }
10
11    protected override Node CreateTree()
12    {
13        Node attackSequence = new Sequence(new List<Node>
14        {
15            new TakeDistance(this),
16            new Aim(this),
17            new Selector(new List<Node>
18            {
19                new Sequence(new List<Node>
20                {
21                    new NeedReload(this),
22                    new Reload(this)
23                }),
24                new Fire(this)
25            })
26        });
27
28        Node root = new Sequence(new List<Node>
29        {
30            new Clear(this),
31            new Selector(new List<Node>
32            {
33                new Sequence(new List<Node>
34                {
35                    new IsHealthLow(this, 30),
36                    new Selector(new List<Node>
37                    {
38                        new Sequence(new List<Node>
39                        {
40                            new IsDetectEnemy(this),
41                            new Selector(new List<Node>
42                            {
43                                new Sequence(new List<Node>
44                                {
45                                    new IsHealthLow(this, 20),
46                                    attackSequence
47                                }),
48                                new Cover(this)
49                            })
50                        }),
51                        new Cover(this)
52                    })
53                }),
54                new Sequence(new List<Node>
55                {
56                    new IsDetectEnemy(this),
57                    attackSequence
58                }),
59                new Sequence(new List<Node>
60                {
61                    new NeedReload(this),
62                    new Reload(this)
63                }),
64                new Selector(new List<Node>
65                {
66                    new Sequence(new List<Node>
67                    {
68                        new IsSeekLevelHigh(this),
69                        new Aim(this),
70                        new Seek(this)
71                    }),
72                    new Sequence(new List<Node>
73                    {
74                        new Walk(this),
75                        new Patrol(this)
76                    })
77                })
78            })
79        });
80
81        return root;
82    }
83}
84