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 Vision & Sound Sensor

Player 나 Object 를 Detect 하는 AISensor 모듈을 만들어 보았다. 두 가지 방법이 떠올랐는데,
  1. Mesh Collider 의 Trigger 이벤트 사용
  2. 원뿔형으로 LineCast 를 쏴서 RaycastHit 로 Detect
그러나 첫번째 방법에는 문제점이 있다.
  • LayerMask 를 활용할 수 없다.
  • 그렇기 때문에 장애물이 센서 시야를 가리게 만들 수 없다.
최종적으로 두번째 방법을 사용했다.

AIVisionSensor

1public class AIVisionSensor : MonoBehaviour
2{
3    public float redZoneDistance = 8.5f;
4    public float yellowZoneDistance = 12.5f;
5
6    [Header("Angle")]
7    [Range(0, 180)]
8    public float horizontalAngle = 20;
9    [Range(0, 180)]
10    public float verticalAngle = 20;
11
12    [Header("Resolution")]
13    [Range(0, 48)]
14    public int horizontalResolution = 10;
15    [Range(0, 48)]
16    public int verticalResolution = 3;
17
18    [Header("Interval")]
19    public float scanInterval = 0.5f;
20    private float scanTimer;
21
22    [Header("Result")]
23    public List<GameObject> yellowZoneObjectList = new();
24    public List<GameObject> redZoneObjectList = new();
25
26    private void Ray(float currentVAngle, float currentHAngle, int v, int h, float distance, List<GameObject> objectList)
27    {
28        Vector3 point = transform.position +
29                        Quaternion.AngleAxis(currentVAngle, transform.right) * Quaternion.AngleAxis(currentHAngle, transform.up) * transform.forward * distance;
30
31        Physics.Linecast(transform.position, point, out RaycastHit hit, 1 << LayerMask.NameToLayer("Obstacle") | 1 << LayerMask.NameToLayer("Object") | 1 << LayerMask.NameToLayer("Ground"));
32
33        if ((h == 0 || h == horizontalResolution - 1) && (v == 0 || v == verticalResolution - 1))
34        {
35            Debug.DrawLine(transform.position, hit.collider ? hit.point : point, Color.white, scanInterval);
36        }
37
38        DebugExtension.DebugPoint(point, Color.white, 0.25f, scanInterval);
39
40        if (hit.collider && hit.collider.gameObject.layer == LayerMask.NameToLayer("Object"))
41        {
42            var detectedObject = hit.collider.gameObject;
43            if (!objectList.Contains(detectedObject)) objectList.Add(detectedObject);
44        }
45    }
46
47    private void Scan()
48    {
49        yellowZoneObjectList.Clear();
50        redZoneObjectList.Clear();
51
52        float currentVAngle = -verticalAngle;
53        float deltaVAngle = (verticalAngle * 2) / (verticalResolution - 1);
54        for (int v = 0; v < verticalResolution; v++)
55        {
56            float currentHAngle = -horizontalAngle;
57            float deltaHAngle = (horizontalAngle * 2) / (horizontalResolution - 1);
58            for (int h = 0; h < horizontalResolution; h++)
59            {
60                Ray(currentVAngle, currentHAngle, v, h, yellowZoneDistance, yellowZoneObjectList);
61                Ray(currentVAngle, currentHAngle, v, h, redZoneDistance, redZoneObjectList);
62
63                currentHAngle += deltaHAngle;
64            }
65            currentVAngle += deltaVAngle;
66        }
67    }
68
69    private void FixedUpdate()
70    {
71        scanTimer -= Time.deltaTime;
72        if (scanTimer < 0)
73        {
74            scanTimer += scanInterval;
75            Scan();
76        }
77    }
78}
79

파라미터

https://velog.velcdn.com/images/lutca1320/post/fc3ebce1-30e7-4606-a01f-089f7b198faf/image.png
  • 위 그림에서 수평 각도가 Horizontal Angle, 수직 각도가 Vertical Angle 값이다.
  • Resolution 은 쏘는 Line 의 개수이므로 해상도를 의미한다.
아래와 같이 파라미터를 조절해 해상도와 각도를 조절할 수 있다.
https://velog.velcdn.com/images/lutca1320/post/6c0af228-454f-47c9-9639-3467f8b6e567/image.gif

Scan 함수

Obstacle 레이어와 Object 레이어의 물체를 LineCast 하고, 그 중 Object 레이어의 물체만 리턴한다. 아래 그림과 같이 장애물이 있을 경우에는 LineCast 가 통과하지 못한다.
플레이어가 장애물 뒤에 있었다면 Detect 되지 않았을 것이다.
https://velog.velcdn.com/images/lutca1320/post/aaae64db-34ce-4aea-8701-ffdec032fc91/image.png

RedZone / YellowZone

https://velog.velcdn.com/images/lutca1320/post/da432e51-2169-4691-9782-714baa8fcc1c/image.png
redZoneDistance 까지가 redZone, redZoneDistance ~ yellowZoneDistance 까지가 yellowZone 이다.
Detect Level 을 나눠두어서 나중에 AI 구현 시 편리하도록 했다.

AISoundSensor

AISoundSensor 스크립트 구현

hearRange : [Listener] 소리를 들을 수 있는 최대 반경 soundRange : [Owner] 소리의 최대 반경
(hearRange + soundRange >= Listener 와 Owner 의 거리) 면, 소리를 Detect 했다고 판단한다.
1public class AISoundSensor : MonoBehaviour
2{
3    private EnemyRobotAI ai;
4    public float hearRange = 20f;
5
6    [Header("Result")]
7    public Vector3 lastDetectedPosition;
8    public GameObject lastDetectedOwner;
9
10    private void Start()
11    {
12        ai = transform.root.GetComponent<EnemyRobotAI>();
13    }
14
15    public void OnSoundHear(float soundRange, Vector3 soundPosition, GameObject owner)
16    {
17        if (Vector3.Distance(transform.position, soundPosition) > soundRange + hearRange) return;
18
19        lastDetectedPosition = soundPosition;
20        lastDetectedOwner = owner;
21        StartCoroutine(ai.SoundReaction(soundPosition));
22    }
23}
24

GunController 수정

FireWeapon 함수가 성공적으로 실행되면 'Robot' 태그를 가진 모든 GameObject 의 OnSoundHear 함수를 호출한다.
1// GunController.cs
2private void FireWeapon()
3{
4    ...
5
6    foreach (var robot in GameObject.FindGameObjectsWithTag("Robot"))
7    {
8        if (robot == transform.root.gameObject) continue;
9
10        var soundSensor = robot.GetComponentInChildren<AISoundSensor>();
11        if (soundSensor) soundSensor.OnSoundHear(fireSoundRange, transform.position, transform.root.gameObject);
12    }
13
14    ...
15}
16