새로운 엔티티(Entity)의 행동은 크게 2가지를 통해 구현할 수 있습니다. 가장 간단한 방법은 onUpdate() 함수 내에서 엔티티(Entity)의 상태를 분석하여 행동을 유도하는 것입니다. 다른 방법은 이 강의에서 알아보겠지만 AI를 사용하는 것입니다. 바닐라(Vanilla)로 예를 들자면, 닭이 알을 낳는 것은 첫 번째 방법으로 구현되어 있습니다. 하지만 플레이어(Player)를 따라오고, 걸어 다니는 것을 포함한 대부분의 행동은 AI를 이용합니다. 교배의 경우에는 첫 번째 방법과 두 번째 방법 모두 구현되어 있으나 AI를 이용한 코드만이 동작하고 있습니다.

이번 강의에서는 AI의 작동 원리와 구현법을 알아 볼 것입니다. 마인크래프트 모드 제작엔티티(Entity)에 대한 기본적인 내용을 숙지하고 계셔야 강의를 온전히 이해하실 수 있습니다.

 

AI의 작동 원리

 

작동 중인 AI와 대기 중인 AI

엔티티(Entity)는 "tasks"라는 리스트(List)에 AI를 저장합니다. 이렇게 저장된 AI는 크게 2가지로 나눌 수 있습니다. 작동 중인 AI와 작동 중이지 않은(대기 중인) AI. 작동 중인 AI는 continueExecuting()이라는 함수를 통해 계속 작동할 것인지를 결정하고, 작동 중이지 않은 AI는 shouldExecute()라는 함수를 통해 작동을 시작할 것인지를 결정합니다. 또한 작동 중인 AI는 updateTask()라는 함수에서 그 내용을 실행하여 원하는 기능대로 동작합니다.

위의 작동 원리는 AI의 개별적인 정보만을 고려한 것입니다. 그러나 실제 동작에는 하나의 엔티티(Entity)라고 할지라도 여러 AI가 동시에 존재하여 서로 충돌의 가능성이 있습니다. 그렇기 때문에 각 AI에는 우선권(Priority)과 함께 isInterruptible, MutexBit의 개념이 있습니다. 이 세 개념은 continueExecuting(), shoudExecute()가 실행될 때, 함께 고려됩니다.

 

사용 가능성

작동 중인 AI가 계속 동작하고, 대기 중인 AI가 새로 실행되기 위해서는 사용 가능성 검사를 통과 해야 합니다. 사용 가능성이란 해당 AI가 동작 중인 모든 AI에 대해 우선권이 같거나 높은 대상과는 호환가능(Compatible) 해야 하고 낮은 대상들은 모두 중지가능(Interruptible) 한 것을 말합니다. 여기서 호환 가능하다는 것은 두 AI가 동시에 동작 가능하다는 것을 의미하고 중지 가능이라는 것은 AI가 동작 중간에 취소되는 것을 허용한다는 의미입니다.

AI 1~3은 동작 중인 AI입니다. 대상 AI는 대기 중인 AI라고 가정합니다. 그렇다면 대상 AI가 AI 2와 호환 불가능이기 때문에 실행이 되지 않을 것입니다. 여담이지만 사용 가능성 검사는 동작 중인 AI를 대상으로 먼저 시행되는데, AI 1~3은 모두 해당 틱(Tick)에서 사용 가능성 검사를 통과하였으므로(= 동작 중이므로) 서로 호환 가능일 것입니다.

계속 위의 상황에서 만약 대상 AI가 동작 중인 AI라고 가정해봅시다. 결론적으로는 AI 2로 인해 대상 AI는 대기 상태로 되돌아 갈 것입니다. 여기서 조금만 더 분석해 봅시다. 대상 AI는 분명 동작 중이었기 때문에 분명 AI 2보다는 먼저 실행되었을 것입니다. 대상 AI가 AI 2와 호환 불가능 관계이고 대상 AI가 이미 작동 중이었는데도 불구하고 AI 2가 실행되었다는 것은, AI 2의 우선권이 대상 AI보다 높고, 대상 AI는 중지 가능한 AI라는 것을 유추할 수 있어야 합니다. 이처럼 우선권이 높은 AI는 중지 가능한 AI를 강제로 종료시킬 수 있습니다.

 

호환 가능성과 중지 가능성

이제 호환 가능성과 중지 가능성에 대해 조금 더 자세히 알아보겠습니다. 중지 가능성은 각 AI 클래스(Class) 내부의 boolean isInterruptible() 함수의 반환 값으로 결정됩니다. 기본적으로 EntityAIBase에는 이 함수가 true를 반환하도록 설정되어 있습니다. 즉, 특별히 오버라이드(Override)하지 않는다면 중지 가능한 AI로 설정됩니다.

호환 가능성은 보다 복잡한 개념인 MutexBit를 사용합니다. MutexBit는 각 2진수의 자리를 통해 해당 AI의 동작을 간단히 분류하는 것이라 할 수 있습니다. 대부분 이동을 요구하는 AI는 1의 MutexBit를 포함합니다. 시선 처리를 요구하는 AI는 2의 MutexBit를 가집니다. 예를 들어 교배를 담당하는 AI는 이동과 시선 모두 요구하므로 (1+2)의 MutexBit를 가집니다.

이동을 요구하는 두 개의 AI는 동시에 실행 될 수 없을 것입니다. 또한 시선 처리를 요구하는 AI도 두 방향을 동시에 바라 볼 수 없으니 마찬가지 입니다. 따라서 두 AI의 MutexBit의 각 2진수 자리를 비교하여 겹치는 자리가 없으면 호환 가능하다고 할 수 있습니다. 수식으로 분석하면bit1 & bit2 == 0일 때 호환 가능한 것 입니다.

 

 

AI 구현 방법

 

지금까지 AI의 작동원리에 대해서 알아봤습니다. 위의 설명에 간혹 실제 함수 이름이 노출되기도 했지만 그것으로는 구현에 부족할 태니 실제 코드를 보며 AI 구현법에 대해 설명하겠습니다. 위의 작동 원리를 명확하게 이해하지 못한다면 원하는 AI를 완벽하게 구현하는 것은 힘듭니다.

 

EntityAIBase를 상속하는 클래스(Class)

AI는 반드시 EntityAIBase를 상속하여 구현됩니다. EntityAIBase는 추상(Abstract) 클래스(Class)이기 때문에 상속 시 반드시 오버라이드(Override) 해야 하는 함수들이 있습니다. 생성자와 함께 다음과 같이 구현합니다.

 

public class EntityAITest extends EntityAIBase {

 

    public EntityAITest() {

        

    }

    

    @Override

    public boolean shouldExecute() {

        // TODO Auto-generated method stub

        return false;

    }

 

}

 

하지만 위의 함수만 입력한다고 원하는 기능을 구현할 수 있는 것이 아닙니다. 따라서 본문에서 잠시 언급되었던 함수들을 모두 오버라이드(Override) 합니다.

 

public class EntityAITest extends EntityAIBase {

 

    public EntityAITest() {

        this.setMutexBits(1);

        // 만약 엔티티(Entity) 이동과 관련된 함수라면 이처럼 MutexBit 1 포함시켜야 합니다.

    }

    

    @Override

    public boolean isInterruptible() {

        // 중지 가능성을 말합니다.

        return super.isInterruptible();

    }

    

    @Override

    public boolean shouldExecute() {

        // AI 대기 상태일 호출되어 새로 실행되어야 하는 조건을 확인합니다.

        return false;

    }

    

    @Override

    public void startExecuting() {

        // shouldExecute() == true 만족하여 AI 새로 동작할 , 처음 실행되는 함수입니다.

        super.startExecuting();

    }

    

    @Override

    public void updateTask() {

        // AI 동작 상태일 (Tick)마다 호출되는 함수입니다.

        super.updateTask();

    }

    

    @Override

    public boolean continueExecuting() {

        // AI 계속 동작할 것인지를 판정합니다.

        return super.continueExecuting();

    }

 

}

 

대부분의 함수가 언급되었고 주석으로 설명을 달아 놓았으니 확인하시길 바랍니다.

 

엔티티(Entity)에게 AI 추가하기

마지막 단계입니다. 성공적으로 AI가 구현되었다면 이제 대상 엔티티(Entity)에게 인공지능을 주입해야 합니다. 위에서 언급되었던 것처럼 'tasks' 리스트(List)에 추가하게 됩니다. 일반적인 경우 엔티티(Entity)의 생성자에서 진행하는 것이 좋습니다.

 

    public EntityHungryChicken(World world) {

        super(world);

        

        this.tasks.addTask(1, new EntityAITest());

        //addTack(우선권, AI)으로 인자(Parameter) 받습니다. 정수만 가능하며 숫자가 작을수록 높은 우선권을 지닙니다.

    }

    

    public boolean isAIEnabled() {

        // 이것이 true 아니면 새로 생성자에서 추가된 AI 작동하지 않습니다.

        return true;

    }

 

여기서 주목해야 할 곳이 두 군데 있습니다. 첫 번째는 addTask()에서 우선권을 지정하는 수는 낮을수록 우선순위가 높다는 점입니다. 두 번째는 isAIEnabled() 함수가 true를 반환하지 않으면 추가된 AI가 작동하지 않는다는 점입니다. 이 두 사항을 모두 확인하여 구현에 어려움이 없으시길 바랍니다.

 

 

 

이것으로 AI에 대한 강의를 마칩니다. 몇 가지 유의할 사항이 있습니다. 일단 위 강의는 tasks 리스트(List)에 추가되는 종류의 AI에 대한 내용입니다. 예를 들어 공격할 상대를 지정하는 AI는 targetTasks에 저장됩니다. 구체적인 구현 방법은 바닐라(Vanilla) 마인크래프트에서 사용된 예들을 살펴보시길 바랍니다. 관련된 개념이나 함수에 대한 질문은 원하시는 만큼 자세히 답변해 드리겠습니다. 감사합니다.

+ Recent posts