[TIL] Obstacle Avoidance

Dreamer·2024년 11월 30일

1. 오늘 주제

오늘은 프로젝트를 진행중인데, 이 프로젝트에 적용하고 싶은 캐릭터들의 AI를 만들어보려고 여러모로 알아봤지만, 사실 모바일 게임에서 구동하는 환경이고 거창하게 AI를 적용 시킬 필요가 있을까 싶었는데 마침 간단하게 구현 할 수 있는 방법을 찾아서 한번 적용을 해보려고 한다. 앞서 말했듯이 여러가지 A*, jPS, JPS(B) 등 여러가지 알고리즘도 찾아보고 자율주행 논문도 찾아보고 했지만, 그정도 깊이로 진행되는 게임은 아니다보니, 일단 Obstacle Avoidance 라는 간단한 알고리즘이라고 하기도 좀 뭐한.. 방법을 남겨 본다.

2. 코드

원본의 내용상 불필요한 부분은 제거하고 필요한 부분만 적용하여 작성한 것이다.


public class CreatureMovement : MonoBehaviour
{
	// 이동속도
    [SerializeField] private float _speed;
	// 회전 속도
    [SerializeField] private float _rotationSpeed;
	// 장애물 감지를 위한 원형 캐스트 반지름
    [SerializeField] private float _obstacleCheckCircleRadius;
	// 장애물 감지를 위한 원한 캐드슽 거리
    [SerializeField] private float _obstacleCheckDistance;
	// 장애물이 속한 레이어
    [SerializeField] private LayerMask _obstacleLayerMask;
    // 추적할 대상
    [SerializeField] private Transform _target;
	
    private Rigidbody2D _rigidbody;
    private Vector2 _targetDirection;
    private RaycastHit2D[] _obstacleCollisions;
    private float _obstacleAvoidanceCooldown;
    private Vector2 _obstacleAvoidanceTargetDirection;

    private void Awake()
    {
        _rigidbody = GetComponent<Rigidbody2D>();
        _targetDirection = transform.up;
        _obstacleCollisions = new RaycastHit2D[10];
    }

    private void FixedUpdate()
    {
        HandleTargeting();
        HandleObstacles();
        RotateTowardsTarget();
        SetVelocity();
    }
    private void HandleTargeting()
    {
        if (_target == null) return;

        _targetDirection = _target.position - transform.position;
    }

    private void HandleObstacles()
    {
        _obstacleAvoidanceCooldown -= Time.deltaTime;

        var contactFilter = new ContactFilter2D();
        contactFilter.SetLayerMask(_obstacleLayerMask);

		// 필터링 된 collider를 _obstacleCollisions에 담아준다.
        int numberOfCollisions = Physics2D.CircleCast(
            transform.position,
            _obstacleCheckCircleRadius,
            transform.up,
            contactFilter,
            _obstacleCollisions,
            _obstacleCheckDistance
        );

        for (int index = 0; index < numberOfCollisions; index++)
        {
            var obstacleCollision = _obstacleCollisions[index];
			// 자기 자신이면 넘어간다.
            if (obstacleCollision.collider.gameObject == gameObject)
            {
                continue;
            }
			// 일정 쿨타임마다 법선벡터를 회피 방향으로 설정한다.
            if (_obstacleAvoidanceCooldown <= 0)
            {
                _obstacleAvoidanceTargetDirection = obstacleCollision.normal;
                _obstacleAvoidanceCooldown = 0.5f;
            }
			// 목표 회전을 회피 방향으로 설정하고, 현재 회전에서 목표회전으로 부드럽게 회전한다.
            var targetRotation = Quaternion.LookRotation(transform.forward, _obstacleAvoidanceTargetDirection);
            var rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, _rotationSpeed * Time.deltaTime);
			// 대상 방향향을 회피 방향으로 설정
            _targetDirection = rotation * Vector2.up;
            // 첫번째 충돌체만 처리하고 넘어감
            break;
        }
    }

    private void RotateTowardsTarget()
    {
    	// 현재 진행 방향에서 회피방향이 적용된 대상 방향으로 회전 값을 다시 계산한다.
        Quaternion targetRotation = Quaternion.LookRotation(transform.forward, _targetDirection);
        // 구해진 목표 회전 방향을 회전 속도를 적용해서 마지막으로 계산한다.
        Quaternion rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, _rotationSpeed * Time.deltaTime);
		// rigidbody를 회전시킨다.
        _rigidbody.SetRotation(rotation);
    }

    private void SetVelocity()
    {
    	// rigidbody에 속도를 적용해서 움직인다.
        _rigidbody.velocity = transform.up * _speed;
    }
}

결과물

  • 두 Agent 가 마주보면서 다가올때 회피

  • 대상이 있을 때 목적지까지 가는데 장애물이 있으면 돌아서 감

이것은 단순하게 대상을 추적하면서 진행 방향에 장애물이 있으면 회피하여 회전 값을 조정하는 방식이 때문에 멀리서 길을 찾아 오는 방식이거나, 복잡한 장애물이 있다면 A* 또는 JPS 같은 길찾기 알고리즘을 적용하고 일정 범위에 도달 시 해당 로직을 적용 하는 방법을 고려해보면 좋을 것 같다.

그리고 생각해보니 지금 진행중인 프로젝트는 회전이 필요없어서 회전하지 않는 방식으로 이 방법을 가지고 다시금 연구를 해야한다. 윽.. ㅋㅋ
그래도 회전하는 방식을 언젠간 써먹을 때가 있을 수 있으니 남겨두는 걸로...!

3. 참조

https://www.youtube.com/watch?v=WK0fBiytW_8

profile
새로운 시작

0개의 댓글