DevLog/유니티 프로젝트

2D RPG - 플레이어를 인식하는 enemy AI

뽀또치즈맛 2026. 1. 13. 13:53

 

Enemy AI가 플레이어를 인식해야

순찰 혹은 IDLE 상태에서 chasing 상태 혹은 attack 상태로 변화할 수 있다.

따라서 적군이 플레이어를 인식하는 것은

가장 필요한 기초적인 단계이다.

 

이러한 단계는 다음과 같이 접근할 수 있다.

 

    protected virtual void OnDrawGizmos()
    {
        Gizmos.DrawLine(groundCheck.position, groundCheck.position + new Vector3(0, -groundCheckDistance));
        Gizmos.DrawLine(primaryWallCheck.position, primaryWallCheck.position + new Vector3(wallCheckDistance * facingDir, 0));

        if (secondaryWallCheck != null)
        {
            Gizmos.DrawLine(secondaryWallCheck.position, secondaryWallCheck.position + new Vector3(wallCheckDistance * facingDir, 0));
        }
    }

에디터(Scene 뷰) 상에 디버그용 선(Gizmos) 을 그려서
캐릭터의 바닥 체크(Ground Check)벽 체크(Wall Check) 범위를 시각화하는 함수이다.

 

실질적으로는 다음과 같은 레이캐스트를 통하여 충돌을 체크하고 있다.

    private void HandleCollisionDetection()
    {
        groundDetected = Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);

        if (secondaryWallCheck != null)
        {
            wallDetected = Physics2D.Raycast(primaryWallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround)
            && Physics2D.Raycast(secondaryWallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);
        }
        else
        {
            wallDetected = Physics2D.Raycast(primaryWallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);
        }

    }

 

 

이제 플레이어를 인식하는 enemy ai를 생성하려면

위와 같은 레이 캐스트를 통하여 제작할 수 있다.

또한 레이 캐스트 범위를 기즈모를 이용하여 시각화하여 가시적으로 확인할 수 있다.

 

 

1.Gizmos 함수 overriding 하기

 

기존의 기즈모 함수를 통하여 가시화한 것과 더불어

플레이어를 체크할 수 있는 거리에 대한 변수를 에디터에서 수정할 수 있도록 

아래와 같이 작성한다.

    [Header("Player detection")]
    [SerializeField] private LayerMask whatIsPlayer;
    [SerializeField] private Transform playerCheck;
    [SerializeField] private float playerCheckDistance = 10;


    public RaycastHit2D PlayerDetection()
    {
        RaycastHit2D hit = Physics2D.Raycast(playerCheck.position, Vector2.right * facingDir, playerCheckDistance, whatIsPlayer | whatIsGround);

        if (hit.collider == null || hit.collider.gameObject.layer != LayerMask.NameToLayer("Player"))
            return default;
            

        return hit;
    }


	protected override void OnDrawGizmos()
    {
        base.OnDrawGizmos();

        Gizmos.color = Color.yellow;
        Gizmos.DrawLine(playerCheck.position, 
        new Vector3(playerCheck.position.x + (facingDir * playerCheckDistance), 
        playerCheck.position.y));

 

 

2. 플레이어 방향 바라보게 하기

public class Enemy_BattleState : EnemyState
{
    private Transform player;
    public Enemy_BattleState(Enemy enemy, StateMachine stateMachine, string animBoolName) : base(enemy, stateMachine, animBoolName)
    {
    }

    public override void Enter()
    {
        base.Enter();

        if(player == null)
            player = enemy.PlayerDetection().transform;
    }
    
    public override void Update()
    {
        base.Update();

        if (WithinAttackRange())
            stateMachine.ChangeState(enemy.attackState);
        else
            enemy.SetVelocity(enemy.battleMoveSpeed * DirectionToPlayer(), rb.linearVelocity.y);
    }

    private bool WithinAttackRange() => DistanceToPlayer() < enemy.attackDistance;
    

    private float DistanceToPlayer()
    {
        if(player == null)
            return float.MaxValue;

        return Mathf.Abs(player.position.x - enemy.transform.position.x);
    }

    private int DirectionToPlayer()
    {
        if(player == null)
            return 0;

        return player.position.x > enemy.transform.position.x ? 1 : -1;
    }
}