선점 행동 나무


25

비헤이비어 트리를 둘러 보려고 시도하고 있으므로 테스트 코드를 스파이 킹하고 있습니다. 내가 어려움을 겪고있는 한 가지는 우선 순위가 높은 것이 나올 때 현재 실행중인 노드를 선점하는 방법입니다.

군인을위한 다음의 단순하고 가상적인 행동 트리를 고려하십시오.

여기에 이미지 설명을 입력하십시오

진드기가 몇 개 지나갔고 근처에 적이 없었고 군인이 잔디 위에 서 있었기 때문에 Sit down 노드가 실행되도록 선택되었습니다.

여기에 이미지 설명을 입력하십시오

이제 싯 다운 액션은 재생할 애니메이션이 있기 때문에 실행하는 데 시간이 걸리므 Running로 상태로 돌아갑니다 . 틱 하나 또는 두 개가지나갑니다. 애니메이션은 여전히 ​​실행 중이지만 적 근처에 있습니까? 조건 노드 트리거. 이제 Sit down 노드를 최대한 빨리 선점 하여 Attack 노드를 실행할 수 있습니다 . 이상적으로 군인은 앉아도 끝나지 않을 것입니다. 그는 단지 앉기 시작하면 애니메이션 방향을 반대로 바꿀 수 있습니다. 사실주의를 더하기 위해 애니메이션에서 요령을 지나친 경우, 앉은 다음 다시 앉게하거나 위협에 대응하기 위해 급히 넘어 질 수도 있습니다.

내가 시도한대로 이런 상황을 처리하는 방법에 대한 지침을 찾지 못했습니다. 지난 며칠 동안 내가 소비 한 모든 문헌과 비디오는이 문제와 관련이 있습니다. 내가 찾은 가장 가까운 것은 실행중인 노드를 재설정하는 개념이지만 Sit 와 같은 노드 는 "이봐 요, 아직 끝나지 않았습니다!"라고 말할 기회를주지 않습니다.

기본 클래스 에서 Preempt()또는 Interrupt()메소드를 정의하려고 생각했습니다 Node. 각기 다른 노드가 자신에게 맞는 방식으로 처리 할 수 ​​있지만이 경우에는 병사를 최대한 빨리 발로 돌려 보내려고합니다 Success. 이 접근법은 또한 내 기지 Node가 다른 행동과 별도로 조건 개념을 가져야 한다고 생각합니다 . 이런 식으로 엔진은 조건 만 검사 할 수 있으며, 통과하는 경우 작업 실행을 시작하기 전에 현재 실행중인 노드를 선점 할 수 있습니다. 이 차별화가 확립되지 않은 경우 엔진은 노드를 무차별 적으로 실행해야하므로 실행중인 노드를 선점하기 전에 새로운 조치를 트리거 할 수 있습니다.

참고로 아래는 현재 기본 클래스입니다. 다시 말하지만 이것은 급격한 일이므로 가능한 한 간단하게 유지하고 필요할 때만 복잡성을 추가하려고 노력했으며 이해했을 때 지금 당장 어려움을 겪고 있습니다.

public enum ExecuteResult
{
    // node needs more time to run on next tick
    Running,

    // node completed successfully
    Succeeded,

    // node failed to complete
    Failed
}

public abstract class Node<TAgent>
{
    public abstract ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard);
}

public abstract class DecoratorNode<TAgent> : Node<TAgent>
{
    private readonly Node<TAgent> child;

    protected DecoratorNode(Node<TAgent> child)
    {
        this.child = child;
    }

    protected Node<TAgent> Child
    {
        get { return this.child; }
    }
}

public abstract class CompositeNode<TAgent> : Node<TAgent>
{
    private readonly Node<TAgent>[] children;

    protected CompositeNode(IEnumerable<Node<TAgent>> children)
    {
        this.children = children.ToArray();
    }

    protected Node<TAgent>[] Children
    {
        get { return this.children; }
    }
}

public abstract class ConditionNode<TAgent> : Node<TAgent>
{
    private readonly bool invert;

    protected ConditionNode()
        : this(false)
    {
    }

    protected ConditionNode(bool invert)
    {
        this.invert = invert;
    }

    public sealed override ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard)
    {
        var result = this.CheckCondition(agent, blackboard);

        if (this.invert)
        {
            result = !result;
        }

        return result ? ExecuteResult.Succeeded : ExecuteResult.Failed;
    }

    protected abstract bool CheckCondition(TAgent agent, Blackboard blackboard);
}

public abstract class ActionNode<TAgent> : Node<TAgent>
{
}

누구든지 올바른 방향으로 나를 인도 할 수있는 통찰력이 있습니까? 내 생각이 올바른 방향을 따르고 있습니까, 아니면 두려워하는만큼 순진합니까?


이 문서를 봐야합니다 : chrishecker.com/My_liner_notes_for_spore/… 여기에서 그는 상태 머신과 같은 것이 아니라 각 진드기의 ROOT에서 나무가 어떻게 걷는 지 설명합니다. BT에는 예외 또는 이벤트가 필요하지 않습니다. 그들은 본질적으로 풀링 시스템이며 항상 뿌리에서 흘러 내림으로써 모든 상황에 반응합니다. 우선 순위가 어떻게 작동하는지, 우선 순위가 높은 외부 조건이 확인되면 흐름이 흐릅니다. ( Stop()활성 노드를 종료하기 전에 일부 콜백 호출 )
v.oddou

aigamedev.com/open/article/popular-behavior-tree-design 도 매우 자세하게 설명되어 있습니다
v.oddou

답변:


6

나는 당신과 같은 질문을 스스로 발견 했으며이 블로그 페이지 의 의견 섹션 에서 문제의 또 다른 해결책을 제공 한 짧은 대화를했습니다 .

첫 번째는 동시 노드를 사용하는 것입니다. 동시 노드는 특수 유형의 복합 노드입니다. 사전 조치 점검 순서와 단일 조치 노드로 구성됩니다. 동작 노드가 '실행 중'상태 인 경우에도 모든 하위 노드를 업데이트합니다. (시작해야하는 시퀀스 노드와 달리 현재 실행중인 자식 노드에서 업데이트됩니다.)

주요 아이디어는 조치 노드에 대해 "취소"및 "취소"라는 두 가지 리턴 상태를 작성하는 것입니다.

동시 노드에서 전제 조건 점검 실패는 실행중인 조치 노드의 취소를 트리거하는 메커니즘입니다. 조치 노드에 장기 실행 취소 논리가 필요하지 않으면 즉시 '취소됨'을 리턴합니다. 그렇지 않으면 작업을 올바르게 중단하기 위해 필요한 모든 논리를 넣을 수있는 '취소'상태로 전환됩니다.


GDSE에 오신 것을 환영합니다. 해당 블로그에서 여기까지 그리고 해당 블로그에 대한 링크에서 해당 답변을 열 수 있다면 좋을 것입니다. 링크는 여기에서 완전히 대답하면 죽는 경향이 있습니다. 질문에 8 표가 남았으므로 정답이 굉장합니다.
카투

행동 트리를 유한 상태 머신으로 되 돌리는 것은 좋은 해결책이라고 생각하지 않습니다. 귀하의 접근 방식은 각주의 모든 출구 조건을 구상 해야하는 것처럼 보입니다. 이것이 실제로 FSM의 단점이라면! BT는 루트에서 다시 시작할 수 있다는 장점이 있으므로 완전히 종료 된 FSM을 암시 적으로 생성하여 종료 조건을 명시 적으로 작성하지 않아도됩니다.
v.oddou 2019

5

나는 당신의 군인이 마음과 몸으로 (그리고 그 밖의 무엇이든) 분해 될 수 있다고 생각합니다. 이후 신체는 다리와 손으로 분해 될 수 있습니다. 그런 다음 모든 부품에는 고유 한 동작 트리와 상위 또는 하위 부품의 요청에 대한 공용 인터페이스가 필요합니다.

따라서 모든 단일 작업을 미세 관리하는 대신 "바디, 잠시 동안 앉아"또는 "바디에서 실행"과 같은 인스턴트 샷 메시지를 보내면 바디는 애니메이션, 상태 전환, 지연 및 기타 작업을 관리합니다. 당신.

또는 신체가 이와 같은 행동을 자체적으로 관리 할 수도 있습니다. 명령이 없으면 "여기에 앉을 수 있을까요?"라고 물을 수 있습니다. 더 흥미롭게도 캡슐화로 인해 피곤함이나 기절과 같은 기능을 쉽게 모델링 할 수 있습니다.

코끼리를 좀비의 지능으로 만들거나, 인간에게 날개를 추가하거나 (그는 눈치 채지 못할 수도 있음) 그 밖의 다른 것들도 교환 할 수 있습니다.

이렇게 분해하지 않으면 조만간 조합 폭발을 만날 위험이 있습니다.

또한 : http://www.valvesoftware.com/publications/2009/ai_systems_of_l4d_mike_booth.pdf


감사. 답을 3 번 읽은 후 이해합니다. 이번 주말에 그 PDF를 읽겠습니다.
가구 있구만

1
지난 1 시간 동안 이것에 대해 생각한 후에, 나는 정신과 신체를 위해 완전히 분리 된 BT와 하위 트리로 분해되는 단일 BT (빌드 타임 스크립트가있는 특수 데코레이터를 통해 참조)와의 구분을 이해하지 못합니다. 모든 것을 하나의 큰 BT로 묶는 것). 이것은 유사한 추상화 이점을 제공하고 실제로 여러 개별 BT를 살펴볼 필요가 없기 때문에 주어진 엔티티가 어떻게 동작하는지 이해하는 것이 더 쉬울 것 같습니다. 그러나 나는 아마도 순진 할 것입니다.
가구 있구만

@ user13414 차이점은 간접 액세스를 사용하는 경우 (예 : 바디 노드가 트리를 다리를 나타내는 오브젝트에 요청해야하는 경우) 충분하고 추가 두뇌가 필요하지 않은 경우 트리를 빌드하려면 특수 스크립트가 필요하다는 것입니다. 코드가 적고 오류가 적습니다. 또한 런타임시 (쉽게) 하위 트리를 전환하는 기능이 손실됩니다. 이러한 유연성이 필요하지 않더라도 실행 속도를 포함하여 아무것도 잃지 않습니다.
Shadows In Rain

3

어젯밤에 침대에 누워서, 나는 내 질문에 기대어 복잡성을 소개하지 않고 어떻게 이것에 대해 갈 수 있을지에 대한 주현절을 가지고 있었다. 여기에는 (잘못 명명 된 IMHO) "병렬"합성물이 사용됩니다. 내가 생각하는 것은 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

다행히도 여전히 읽을 수 있습니다. 중요한 점은 다음과 같습니다.

  • 앉아서 / 지연 / 일어나 시퀀스 병렬 시퀀스 (내의 서열이다 ). 모든 진드기마다 병렬 시퀀스는 적의 근접 상태 (반전)를 확인합니다 . 적을 경우 입니다 근처, 조건은 실패하고 너무 너무 전체 병렬 시퀀스를 수행합니다 (즉시, 아이 시퀀스를 통해 중간 경우에도 앉아 아래로 , 지연 , 또는 일어나 )
  • 장애가 발생 하면 병렬 시퀀스 위 의 선택기 B 가 중단을 처리하기 위해 선택기 C 로 내려갑니다 . 병렬 시퀀스 A 가 성공적으로 완료되면 선택기 C 가 실행되지 않습니다.
  • 그러면 셀렉터 C 가 정상적으로 일 어설려고하지만 군인이 현재 일어 서기 어려운 위치에있는 경우 비틀 거리는 애니메이션을 트리거 할 수도 있습니다.

나는 내가 생각했던 것보다 조금 더 지저분하지만 이것이 효과가 있다고 생각합니다 (곧 스파이크에서 시도 할 것입니다). 좋은 점은 하위 트리를 재사용 가능한 논리로 캡슐화하여 여러 지점에서 참조 할 수 있다는 것입니다. 그것은 내 대부분의 관심사를 완화시킬 것이므로 이것이 가능한 해결책이라고 생각합니다.

물론, 나는 이것에 대해 누군가 생각이 있다면 여전히 듣고 싶습니다.

업데이트 :이 접근법은 기술적으로 효과가 있지만 sux로 결정했습니다. 관련없는 하위 트리는 자체 트리의 다른 부분에 정의 된 조건을 "알아야"하여 자체적으로 소멸 될 수 있기 때문입니다. 하위 트리 참조를 공유하면 이러한 고통을 완화 할 수 있지만 행동 트리를 볼 때 기대하는 것과는 여전히 상반됩니다. 사실, 나는 아주 간단한 스파이크에서 같은 실수를 두 번했다.

따라서 객체 경로 내에서 선점에 대한 명시 적 지원과 선점이 발생할 때 다른 동작 집합을 실행할 수있는 특수한 합성 경로로 넘어가겠습니다. 나는 뭔가 일이있을 때 별도의 답변을 게시 할 것입니다.


1
만약 당신이 정말로 서브 트리를 재사용하기 원한다면 언제 인터럽트 할 지에 대한 논리 ( "여기서 가까운")는 아마도 서브 트리의 일부가 아니어야합니다. 대신 시스템은 우선 순위가 높은 자극으로 인해 하위 트리 (예 : 여기 B)에게 자체 인터럽트를 요청할 수 있으며, 특정 표준 상태로 캐릭터를 다시 가져 오는 것을 처리하는 특수하게 표시된 중단 노드 (C 여기)로 점프합니다. 예를 들어 서 있습니다. 예외 처리와 동등한 비헤이비어 트리와 비슷합니다.
Nathan Reed

1
어떤 자극이 중단되는지에 따라 여러 개의 중단 처리기를 통합 할 수도 있습니다. 예를 들어, NPC가 앉아 화재를 일으키기 시작하는 경우, 상대방이 일어 서지 않고 (더 큰 목표물을 제시하는) 것이 아니라 낮은 자세를 유지하고 뒤덮이기를 원합니다.
Nathan Reed

@Nathan : "예외 처리"에 대해 언급 한 것이 재미 있습니다. 내가 지난 밤에 생각한 첫 번째 가능한 접근법은 Preempt 컴포지트에 대한 아이디어 였는데, 여기에는 두 명의 자녀가 있습니다 : 하나는 정상적인 처형과 다른 하나는 선점 된 처형. 정상적인 자녀가 합격하거나 실패하면 그 결과가 전파됩니다. 선점 아동은 선점이 발생한 경우에만 실행됩니다. 모든 노드 Preempt()에는 트리를 통해 트리클을 하는 메소드 가 있습니다 . 그러나 이것을 실제로 "처리"하는 것은 선점 컴포지트 (preempt composite) 일 뿐이며, 선점 하위 노드로 즉시 전환됩니다.
가구 있구만

그런 다음 위에서 설명한 병렬 접근 방식을 생각했는데 API 전체에 추가 크래프트가 필요하지 않기 때문에 더 우아해 보였습니다. 하위 트리 캡슐화에 관해서는 복잡성이 발생할 때마다 가능한 대체 지점이라고 생각합니다. 자주 확인되는 여러 조건이있는 곳일 수도 있습니다. 이 경우, 치환의 근본은 여러 조건을 자식으로 갖는 서열 합성물입니다.
가구 있구만

서브 트리는 실행 전에 "적중"해야하는 조건을 아는 것이 자명하고 매우 명시 적이거나 암시 적이므로 완벽하게 적합하다고 생각합니다. 더 큰 관심사 인 경우 하위 트리에 조건을 유지하지 말고 "콜 사이트"에 조건을 유지하십시오.
세이 반

2

여기에 내가 해결 한 해결책이 있습니다 ...

  • 내 기본 Node클래스에는 Interrupt기본적으로 아무것도하지 않는 메소드가 있습니다.
  • 조건은 반환해야한다는 점에서 "일등"구성입니다 bool(따라서 실행 속도가 빠르고 업데이트가 두 개 이상 필요하지 않음을 의미 함)
  • Node 하위 노드 모음에 개별적으로 조건 모음을 노출합니다.
  • Node.Execute모든 조건을 먼저 실행하고 조건이 실패하면 즉시 실패합니다. 조건이 성공하거나 존재 ExecuteCore하지 않으면 서브 클래스가 실제 작업을 수행 할 수 있도록 호출 합니다. 다음과 같은 이유로 조건을 건너 뛸 수있는 매개 변수가 있습니다.
  • Node또한 CheckConditions메소드 를 통해 조건을 개별적으로 실행할 수 있습니다 . 물론 Node.Execute실제로는 CheckConditions조건을 확인해야 할 때 호출 합니다.
  • Selector합성물은 이제 CheckConditions실행을 고려하는 각 자식을 요구 합니다. 조건이 실패하면 다음 자식으로 바로 이동합니다. 그들이 통과하면 이미 실행중인 자식이 있는지 확인합니다. 그렇다면, 그것은 호출 Interrupt실패 후합니다. 그것이 현재 실행중인 노드가 인터럽트 요청에 응답하기를 희망하기 때문에이 시점에서 할 수있는 전부입니다.
  • 나는 Interruptible꾸며진 자식으로 규칙적인 논리 흐름을 가지고 있기 때문에 특별한 데코레이터 인 노드를 추가 한 다음 중단을위한 별도의 노드를 추가했습니다. 중단되지 않는 한 일반 자식을 완료 또는 실패로 실행합니다. 중단되면 즉시 중단 처리 하위 노드 실행으로 전환됩니다. 하위 노드는 필요한만큼 복잡 할 수 있습니다.

최종 결과는 스파이크에서 가져온 것과 같습니다.

여기에 이미지 설명을 입력하십시오

위는 벌을위한 행동 트리로, 꿀을 모아 벌집으로 돌려 보냅니다. 꿀이없고 꽃이있는 꽃 근처에 있지 않으면 방황합니다.

여기에 이미지 설명을 입력하십시오

이 노드가 중단 가능하지 않으면 절대 실패하지 않으므로 벌은 끊임없이 방황합니다. 그러나 부모 노드는 선택자이며 우선 순위가 높은 자식이므로 실행 자격이 지속적으로 확인됩니다. 조건이 충족되면 선택기가 중단을 일으키고 위의 하위 트리가 즉시 "중단 된"경로로 전환됩니다.이 경로는 실패하여 최대한 빨리 중단됩니다. 물론 다른 조치를 먼저 수행 할 수는 있지만 스파이크에는 보석 이외의 다른 일이 실제로 없습니다.

그러나 이것을 내 질문에 다시 연결하기 위해, "중단 된"경로는 앉은 애니메이션을 뒤집어 놓을 수 있으며, 실패하면 군인이 걸려 넘어 질 수 있다고 상상할 수 있습니다. 이 모든 것이 우선 순위가 높은 상태로의 전환을 유지하며 이것이 바로 목표였습니다.

나는 생각 하지만, 솔직히 말해서,이 조건과 행동의 구체적인 구현의 확산에 대한 추가 질문을 제기하고, 애니메이션 시스템으로 동작 트리를 묶는 것 - 내가 위의 개요, 특히 핵심 조각 -이 접근 방식에 만족 해요. 나는 아직도이 질문들을 분명하게 표현할 수 있을지 확신하지 못하므로 계속 생각 / 스파이 킹 할 것입니다.


1

"When"데코레이터를 발명하여 동일한 문제를 해결했습니다. 조건과 두 가지 하위 동작 ( "then"및 "otherwise")이 있습니다. "When"가 실행되면 상태를 확인하고 그 결과에 따라 자식을 실행합니다. 조건 결과가 변경되면 실행중인 자식이 재설정되고 다른 분기에 해당하는 자식이 시작됩니다. 자식이 실행을 마치면 "When"전체가 실행을 완료합니다.

요점은 시퀀스 시작시에만 조건을 확인하는이 질문의 초기 BT와 달리 내 "When"는 실행중인 상태를 계속 확인한다는 것입니다. 따라서 비헤이비어 트리의 상단은 다음과 같이 바뀝니다.

When[EnemyNear]
  Then
    AttackSequence
  Otherwise
    When[StandingOnGrass]
      Then
        IdleSequence
      Otherwise
        Hum a tune

고급 "When"사용법의 경우, 지정된 시간 동안 또는 무기한으로 (부모 동작에 의해 재설정 될 때까지) 아무 것도 수행하지 않는 "Wait"조치를 도입하려고합니다. 또한 "When"분기가 하나만 필요한 경우 다른 분기에는 "성공"또는 "실패"작업이 포함될 수 있으며 해당 작업은 즉시 성공하고 실패합니다.


이 접근법은 BT의 최초 발명가들이 생각했던 것과 더 가깝다고 생각합니다. 보다 역동적 인 흐름을 사용하므로 BT에서 "실행 중"상태가 매우 위험한 상태이므로 매우 드물게 사용해야합니다. BT는 항상 루트에서 언제든지 다시 올 가능성을 염두에두고 설계해야합니다.
v.oddou 2019

0

늦었지만 이것이 도움이되기를 바랍니다. 주로 내가 이것을 알아 내려고 노력하면서 개인적으로 뭔가를 놓친 것을 확인하고 싶지 않기 때문에. 나는 주로이 아이디어를 빌려 Unreal왔지만 Decorator,베이스에 속성을 만들 Node거나,에 강하게 묶지 Blackboard않고 더 일반적입니다.

이것은라는 새로운 노드 유형 소개합니다 Guard(A)의 조합처럼 Decorator, 그리고 Composite하고있다 condition() -> Result함께 서명을update() -> Result

Guard반환시 Success또는 취소시 취소 방법을 나타내는 세 가지 모드가 있습니다 Failed. 실제 취소는 발신자에 따라 다릅니다. 따라서 Selector전화 Guard:

  1. 취소 .self -> Guard(및 실행중인 자식)이 실행 중이고 상태가Failed
  2. 취소 .lower-> 우선 순위가 낮은 노드가 실행 중이고 상태가 Success또는Running
  3. 취소 .both -> 모두 .self.lower조건에 따라와 노드를 실행. 실행중인 경우 자체를 취소하고 조건 falseComposite규칙 인 Selector경우 규칙 에 따라 ( 이 경우에는) 우선 순위가 낮은 것으로 간주되는 경우 실행중인 노드를 조건화 하거나 취소합니다 Success. 다시 말해, 기본적으로 두 개념이 결합 된 것입니다.

좋아 Decorator하고 달리 Composite그것은 단 하나의 아이 만 필요합니다.

하지만 Guard단지 많은, 당신이 중첩 할 수있는 하나의 아이를 가지고 Sequences, Selectors또는 다른 종류의 Nodes기타를 포함하여 당신이 원하는대로, Guards또는 Decorators.

Selector1 Guard.both[Sequence[EnemyNear?]] Sequence1 MoveToEnemy Attack Selector2 Sequence2 StandingOnGrass? Idle HumATune

위 시나리오에서 Selector1업데이트 할 때마다 항상 하위와 관련된 가드에 대한 상태 확인이 실행됩니다. 위의 경우, Sequence1Guarded이며 작업을 Selector1계속 하기 전에 확인해야 running합니다.

때마다 Selector2또는 Sequence1즉시 실행 EnemyNear?반환 success동안 Guards condition()다음 검사 Selector1인터럽트를 발행합니다 /를에 취소 running node한 후 평소와 같이 계속합니다.

다시 말해, 우리가 행동하는 것보다 행동을 훨씬 더 반응 적으로 만드는 몇 가지 조건에 따라 "유휴"또는 "공격"분기에 반응 할 수 있습니다. Parallel

또한 Node우선 순위가 높은 싱글 을 Nodes같은 곳에서 달리지 않도록 보호 할 수 있습니다Composite

Selector1 Guard.both[Sequence[EnemyNear?]] Sequence1 MoveToEnemy Attack Selector2 Guard.both[StandingOnGrass?] Idle HumATune

경우 HumATune장기 실행은 Node, Selector2항상이 아니었다 첫번째 경우 하나를 확인합니다 Guard. 따라서 npc가 잔디 패치로 순간 이동 한 경우 다음 에 실행하면 실행을 위해 Selector2확인 Guard하고 취소 HumATune합니다.Idle

이 순간 이동 도착하면 밖으로 잔디 패치, 그것은 실행중인 노드를 (취소합니다 Idle) 및 이동HumATune

여기서 알 수 있듯이, 의사 결정 GuardGuard그 자체가 아닌 호출자에 의존 합니다. 누가 고려 되는가의 규칙은 lower priority발신자와 함께 남아 있습니다 . 두 예에서 모두로 Selector구성되는 것을 정의 하는 사람은 누구 lower priority입니다.

Composite호출 된 경우 Random Selector해당 특정 구현 내에서 규칙을 정의하게 Composite됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.