슬라이딩 도어를 이용하는 케릭터 구현

Unity3D/Problems 2015. 11. 11. 19:24
반응형

2015/11


Unity 5.2.1f



Subjects 

- Animation Event

- Rewind animation 

- animation state speed 

- animation state time


- delegate , event queuing 


- Trouble : Door , Character 



요구사항 

층으로 구분되어 지는 공간이 존재 한다.

각 층은 1개의 문을 가지고있다.




애니메니션으로 되어 있는 문이 있다 

이 문은 2가지 Open과 Close이름을 가진 2가지 애니메이션이 있다.




(위 그림은 문이 열리고 닫히는 애니메이션의 캡쳐이다.)


케릭터들은 이 문을 통해 들어오고 나오기를 반복 한다.

타입 A의 케릭은 이 문을 통해 들어오고 나간다.

타입 B의 케릭은 이 문을 통해 다음층으로 이동 할수 있다.



문이 완전히 열리면 케릭터는 문을 통해 나가던지, 다음층의 문을 열수있다.

타입 A의 케릭은 문을 열고 나가면 되지만 타입 B의 케릭은 다음층의 문이 완전히 열릴때까지 기다렸다가 다음층의 문앞으로 순간이동? 해야 했다.


(스크립트 : 이벤트 )



Door : MonoBehaviour            (문 오브젝트를 컨트롤)

SimpleISODoorEvent : MonoBehaviour  (문이 완전히 열렸을때 이벤트를 받아 Door클래스에게 전달)

Factory : MonoBehaviour     (케릭터를 생성하는 역할)

Character : MonoBehaviour     (비주얼적으로 이동)

- CharacterController         (이동하는 로직)


두가지 스크립트를 작성 했다.

아직도 Door는 MonoBehaviour를 상속 해야만 했나 고민중이다.

현재는 Update구문에서 이벤트를 Dequeue하는데 사용중이지만 이것이 진짜 필요한 이유인지는 고민봐야 한다. 이것이 필요 없는 작업이라면 MonoBehaviour를 상속을 받지 않을것이다.


애니메이션에 문이 완전히 열렸을때 이벤트를 달아 보기로 했다.



애니메이션패널을 열어 이벤트를 다는 방법도 있었지만 제작되어 있었던 리소스를 재 가공한다는것에 대한 불편함이 있어 스크립트를 통해 이벤트를 다는 방법을 선택 했다.


"OpenComplete" 이름의 함수가 문이 완전히 열렸을때 호출될수 있게 애니메이션 프리팹에 MonoBehaviour를 상속받은 스크립트를 Assign해야 한다. 스크립트의 내용은 별거 없다.



assign한 이벤트 스크립트에서 문이 완전히 열렸을때 Door라는 클래스에서관리 할수 있게 이벤트 구독을 시작 한다.


   var evt = animation.GetComponent<SimpleISODoorEvent>();

        evt.OnOpenCompleteEventHandler += evt_OnOpenCompleteEventHandler;


문이 완전히 열렸을때 몇번째 층의 문인지 알아야 했기 때문에 다음과 같이 intParameter 속성을 사용했다.


   var openClip = animation.GetClip("doorslideopen");

        var evtOpen = new AnimationEvent();

        evtOpen.time = animation["doorslideopen"].length;

        evtOpen.intParameter = this.floor;

        evtOpen.functionName = "OpenComplete";

        openClip.AddEvent(evtOpen);


이벤트 등록과 구독이 모두 끝났다.



(애니메이션)


고민해봐야 했었던 것은 문이 열리는 도중에 다른 케릭터가 문을 열기를 시도 하거나 

문이 닫히는 도중에 다른 케릭터가 문을 열기를 시도할때 

문이 완전히 열렸을때 문을 열려고 할때 

그리고 문이 완전히 닫혔을때 문을 열려고 할때였다.



Door 클래스는 Open과 Close 2가지 메서드를 노출한다 

처음에는 Open(), Close()의 형태의 단순히 문을 여닫는 기능만 있었다.


시나리오는 이렇다


케릭터는 A타입과 B타입으로 구분된다.

A타입은 문을 통해 들어오고 문을 통해 나간다 (Destroy)

B타입은 문을 통해 들어와서 상주해있는 동안 다른층으로 이동할수 있다 이때 문을 통해 이동한다.


Factory에는 A타입의 케릭터를 생성한다.

해당 층에 문이 열리고 케릭터가 나온다. 

해당 층의 문이 닫힌다.

케릭터가 있는 층의 공간에서 임의 이동 패턴에 의해 움직인다.

패턴이 끝나면 해당 층의 문으로 이동한다 

해당 층의 문을 연다

나간다 (Destroy)

해당 층의 문을 닫는다.



케릭터가 문이 열리는 이벤트를 구독하게 되면 문이 열릴때 모든 케릭터들이 문이 열리는 이벤트를 받게 된다. 케릭터를 생성하는 Factory에서만 이벤트를 받게끔 Factory에서 이벤트를 구독하기로 했다.


그러다 보니깐 문을 열때 어떤 이유에서 문을 열었는지를 알필요가 있었다.

그래서 단순한 Open()에서 Open(DoorInfo)으로 변경 하게 되었다.


DoorInfo클래스는 별거 없다 


누가, 몇층으로, 무엇을 ... 이 세가지를 담고 있는 데이터 클래스이다.


    public class DoorInfo {

        public SimpleISOCharacter character { get; private set; }

        public int floor { get; private set; }

        public string eventName { get; private set; }

        public DoorInfo(SimpleISOCharacter character, int floor, string eventName)

        {

            this.character = character;

            this.floor = floor;

            this.eventName = eventName;

        }

    }


이제 Factory에서 문을 열때 케릭터를 먼저 생성하고 (문이 그냥 열릴일이 없다 사람이 있고 사람이 문을 여는거니깐 케릭터의 생성이 사실상 먼저이다.)


케릭터A 생성 (1층, x, x위치)

1층의 문을 연다 ( 케릭터, 1층, 케릭터A생성됨)

문을열게 되면 문 애니메이션이 실행되고 문이 완전히 열렸을때 이벤트는 다시 Door클래스에서 전달되며 Factory에서 다시 문이 완전히 열렸을때 이벤트를 받을수 있다.

이때 어떤이유에서 열렸는지 알수 있음으로 다음동작을수행할수 있다.


public void OpenDoor(DoorInfo info)

    {

        infoQueue.Enqueue(info);


        if (isFullyOpened)

        {

            eventQueue.Enqueue(OnOpenEventHandler);

        }

        else

        {

            if (animation.IsPlaying("doorslideopen"))

            {

                var progressed = float.Parse(openAnimationState.time.ToString("F2"));

                isFullyOpened = true;

                eventQueue.Enqueue(OnOpenEventHandler);

            }

            else if (animation.IsPlaying("doorslideclose"))

            {

                animation["doorslideclose"].speed = -closeAnimationState.normalizedTime;

                animation.Play("doorslideclose");

                var progressed = float.Parse(closeAnimationState.time.ToString("F2"));

                StartCoroutine(Wait(progressed, () =>

                {

                    animation.Stop("doorslideclose");

                    isFullyOpened = true;

                    eventQueue.Enqueue(OnOpenEventHandler);

                    CloseDoor();

                }));

            }

            else

            {

                animation["doorslideclose"].speed = 0.5f;

                animation.Play("doorslideopen");

            }


        }

    }


문이 열리고 있을때 다시 문을 연다던가 문이 닫히고 있을때 문을 연다던가의 처리이다.

눈여겨 봐야 할것은 


1. 문이 열리는중이라면 

                var progressed = float.Parse(openAnimationState.time.ToString("F2"));

                isFullyOpened = true;

                eventQueue.Enqueue(OnOpenEventHandler);


2. 문이 닫히는 중이라면 

                animation["doorslideclose"].speed = -closeAnimationState.normalizedTime;

                animation.Play("doorslideclose");

                var progressed = float.Parse(closeAnimationState.time.ToString("F2"));

                StartCoroutine(Wait(progressed, () =>

                {

                    animation.Stop("doorslideclose");

                    isFullyOpened = true;

                    eventQueue.Enqueue(OnOpenEventHandler);

                    CloseDoor();

                }));



이다.


첫째,

사실 문이 열리는중에 다시 연다면 문을 열기만 하고 애니메이션이 끝났을때 이벤트가 2번 발생할것으로 생각했던게 실수였다.

애니메이션은 한번만 실행하기 때문에 이벤트는 1회 발생한다.

그래서 문이 열리고있을경우 다시 문을 열었을때 남은시간 후에이벤트를 강제로 발생시켜야 했다. 이문제때문에 많은시간을 허비 했다.


둘째, 애니메이션이 Rewind시키는 문제인데 

animation["doorslideclose"].speed = -closeAnimationState.normalizedTime;

남은 시간만큼 speed를 -로 해줘야 한다는것이다 처음에는 -를 빼먹고 했더니 애니메이션이자연스럽지 못했다.


이외에 문이 각층에 하나씩 있기 때문에 문이 열릴때 각 문은 자신의 층의 문이 열린것인지 확인 해야 하는 과정이 필요 했으며 

Factory에서 문이 열렸을때 이벤트를 구독 처리 하는 방법은 

private void OnOpenEventHandler(SimpleISODoor.DoorInfo info)

    {

        switch (info.eventName)

        {

            case "OpenedCurrentDoor":

                break;


            case "OpenedNextDoor":

                break;

... 블라블라 식으로 처리 했다.



애니메이션에서 이벤트를 받아 무언가 처리 해야 할일이 있을때 이와같은 문제를 인식하고 있으면 좋을것같아 글을 써봤다.


반응형
: