Unity Engine2012.06.20 11:21

1. FSM 구현을 위해 Unity3D engine 이 제공하는 StartCoroutine 메서드 활용.

Unity > Support > Script Reference > MonoBehaviour.StartCoroutine 참고

Unity3D engine 에서 제공하는 MonoBehaviour 객체의 StartCoroutine 메서드는 매 프레임 마다 지정 된 콜백함수를 실행합니다.

Debug.Log 를 찍어서 확인 해 보시면, Update, StartCoroutine 에서 지정 된 콜백함수 순으로 실행이 됩니다.

이때 지정 된 콜백 함수가 IEnumerator 를 반환하는 형태이기 때문에 while 문과 yield 키워드를 통해 FSM 을 구현 할 수 있습니다.

 

2. State 정의 및 IEnumerator 를 반환하는 State 별 함수 구현.

  - State 정의

    우선, FSM 이 되는 객체에 State 를 정의 합니다. (귀찮아서 세밀하게 구현 하는 것보단 심플하게 어떻게 동작하는지 보여드리는게 더 효과적일 것 같아서 Init, Idle, Move 로만 정의했습니다.)

 

public enum DudeState {
    Init,
    Idle,
    Move
}

 

  - State 별 함수 구현

    FSM을 구현하기 위해 3가지의 메서드가 필요합니다. (State 최초 진입 시 실행 될 메서드, 매 프레임마다 실행 될 메서드, State 종료시 실행 될 메서드)

    만약, StartCoroutine 메서드가 없다면 각 메서드를 구현 해 주고, State 변경 메서드를 따로 두어 관리해야 하겠지만, Unity3D engine 을 사용 시에는 더욱 더 편하게 3개의 메서드를 하나로 통합 할 수 있으며, 클래스 상속을 통해 메서드를 상속, 오버라이드 하여 유연하게 구현하는 장점이 생기죠.

방법은 다음과 같습니다.

 

IEnumerator MoveState() {
    // Enter
    this.gameObject.animation.Play("Take 001");
    yield return null;

    while (this.State == DudeState.Move) {
        // Excute
        this.gameObject.transform.position +=
            new Vector3(0, 0, Time.deltaTime * moveSpeed);

        if (!moving) {
            this.State = DudeState.Idle;
        }

        yield return null;
    }

    // Exit
    NextState();
}

 

    IEnumerator 를 반환하고 yield 키워드를 사용하는 이유는 이렇습니다.

    StartCoroutine에 콜백함수가 지정되면, 지정 된 콜백함수는 매프레임마다 Update 문 다음에 실행됩니다. 이 때, yield 문을 만나면 콜백함수 내에서 yield 다음 구문을 다음 프레임으로 양보하고 콜백함수가 종료가 됩니다. 그러면 또 다음번 프레임에서는 yield 문 다음 구문이 실행이 되는 것이죠.

 

    그래서 위와 같이 콜백함수를 코딩하면,

      - State 최초 진입 시 실행 되는 블럭 (Enter 부분, while 문 이전까지)

      - 매 프레임 마다 실행 되는 블럭 (Execute 부분, While 문 블럭)

      - State 종료 시 실행 되는 블럭 (Exit 부분, while문 블럭 이하)

    이렇게 구현이 되는 것이죠.

 

3. 각 State 에 맞는 StartCoroutine 의 콜백함수(2.번에서 구현 된)를 지정.

 

protected void NextState() {
    string methodName = State.ToString() + "State";
    System.Reflection.MethodInfo info = GetType().GetMethod(methodName,
        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    StartCoroutine((IEnumerator)info.Invoke(this, null));
}

 

 

 

 

요약

1. FSM 구현을 위해 Unity3D engine 이 제공하는 StartCoroutine 메서드 활용.

2. State 정의 및 IEnumerator 를 반환하는 State 별 함수 구현.

3. 각 State 에 맞는 StartCoroutine 의 콜백함수(2.번에서 구현 된)를 지정.

 

 

 

Unity 샘플 프로젝트를 첨부합니다.

FSMSample.7z

 

 

Posted by Min-gu, Kim
Silverlight2009.10.27 11:07
객체지향 언어를 사용하시는 많은 개발자분들은 좀 더 객체지향적인 코드를 원하곤 합니다. 여기서, 좀 더 객체지향적인 코드란 것은 객체지향이 추구하는 언어적인 특색을 잘 살리는 코드를 말하는 것이죠. 그래서, 많은 분들이 클래스 다운 클래스를 만드려고 하고, Design Pattern 등에 관심도 보이고 합니다. 또한, 객체의 생성과 소멸 등에 관해서도 관심을 갖게 되고요 :)

이번 포스팅에서는 Design Pattern 의 하나인 Observer Pattern과 Singleton Pattern을 포스팅 해 보려고 합니다. 그러나, 여기서 Observer Pattern과 Singleton Pattern을 사용하고 안하고의 유무에 따라서 코드가 좀 더 객체지향적이라고 할 수 있는 것은 아닙니다. 좀 더 크게 Design Pattern의 사용 유무가 객체지향적이다 아니다라고 할 순 없다는 것이죠.

또한, Design Pattern 을 접하시는 대부분의 개발자분들은, Design Pattern 에서 정의하는 Command Pattern, Factory Pattern, Observer Pattern 등의 용어만 접할 뿐, 이미 사용하고 있는 로직인 경우도 많습니다. 그래서 어떤 분들은, Design Pattern 을 로직이나 알고리즘 수준이 아닌, 개발자간의 커뮤니케이션에 필요한 용어 정도로 생각하시는 분들도 많습니다.

다만, Design Pattern 을 배움에 있어 객체 지향 언어의 특색을 전제해야 하는 것들이 있습니다. 바로 이런 특색들을 개념적으로 습득하시는 일련의 과정이 큰 도움이 될 것이라 생각합니다.

이번 포스팅에서 소개 해 드릴 Observer Pattern과 Singleton Pattern 은, Interface 나 abstract class 같은 추상 클래스에 대한 개념적 이해를 전제해야 합니다. 그럼, 간단히 Observer Pattern 을 포스팅하기 위한 목차로 시작해 보겠습니다.

1. Interface
2. Observer
3. Observer Control class
4. Singleton Pattern

Interface
우선, 기본적인 문법을 보시면
public interface IBackground
{
    void Changed(Brush _brush);
}
IBackground 라는 interface를 선언 한 뒤 IBackground 블럭 내에서 반환타입이 void 이며 파라미터로 Brush 타입의 인자를 받는다는 메소드의 형식만 선언하게 됩니다.
이 경우 IBackground 를 상속받는 class는 반드시 Changed 메소드를 구현해야 하며, 상속받은 class 는 IBackground 타입으로 형변환이 가능하고, 그 경우 Changed 메소드가 노출되게 됩니다.

사실, 위에서 말씀드린 것 이외에 interface의 다른 큰 특징은 없습니다. 말씀드린게 전부이죠.
헌데, 몇가지 매우 핵심적인 사항이 있습니다.
IBackground 라는 interface를 선언 한 뒤 IBackground 블럭 내에서 반환타입이 void 이며 파라미터로 Brush 타입의 인자를 받는다는 메소드의 형식만 선언하게 됩니다.
이 경우 IBackground 를 상속받는 class는 반드시 Changed 메소드를 구현해야 하며, 상속받은 class 는 IBackground 타입으로 형변환이 가능하고, 그 경우 Changed 메소드가 노출되게 됩니다.
바로, 다음의 3가지 사항입니다.
1. 메소드의 형식만 선언
2. 상속받은 class는 반드시 interface 에 정의 된 메소드를 구현
3. 상속받은 class는 interface 형으로 형변환이 가능

저는, Interface를 가장 쉬이 이해할 수 있는 한 마디가 바로 "표준"이라고 생각하는데요, 예를 들어 USB 포트를 들어 보면..

USB 포트는 일정한 규격, 또는 표준이라고 할 수 있습니다. 어떠한 디바이스 건 USB 포트의 형식에 맞춰 디바이스와 연결할 수 있도록 셋팅 해 둔다면, USB에 꽂는 즉시 디바이스를 인식하고 전원이 공급되게 되죠. 여기서 USB 포트는 표준을 제시하고, 디바이스 벤더는 USB 표준에 맞게 설계를 할 것이고요.

interface도 마찬가지로 "표준"이라고 생각하실 수 있습니다.
BlueUserControl class와 RedUserControl class 두 개의 UserControl 클래스가 있습니다.

<BlueUserControl.cs>
public partial class BlueUserControl : UserControl, IBackground
{
    public BlueUserControl()
    {
        InitializeComponent();
    }

    #region IOption Members 

    public void Changed(Brush _brush)
   {
        this.LayoutRoot.Background = _brush;
    } 

    #endregion
}


<RedUserControl.cs>
public partial class RedUserControl : UserControl, IBackground
{
    public RedUserControl()
    {
        InitializeComponent();
    } 

    #region IOption Members
 
    public void Changed(Brush _brush)
    {
        this.LayoutRoot.Background = _brush;
    } 

    #endregion


두 class 모두 IBackground interface 를 상속 받고, 반드시 구현해야 할 interface 내에서 정의한 메소드인 public void Changed(Brush _brush) 를 구현하고 있습니다.

MainPage의 Grid에 두 클래스를 배치 해 보도록 하죠.
<MainPage.xaml>

<UserControl x:Class="ObserverPattern.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    xmlns:uc="clr-namespace:ObserverPattern.UserControls"

    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

    <Grid x:Name="LayoutRoot" Background="Black">

        <Grid.ColumnDefinitions>

            <ColumnDefinition/>

            <ColumnDefinition/>

        </Grid.ColumnDefinitions>

        <uc:BlueUserControl x:Name="blueUserControl" Grid.Column="0"/>

        <uc:RedUserControl x:Name="redUserControl" Grid.Column="1"/>

        <Button x:Name="OptionChangeButton" Content="Changed" Height="30"

                HorizontalAlignment="Left"

                VerticalAlignment="Top"

                Margin="10,10,0,0"

                Click="OptionChangeButton_Click"/>

    </Grid>

</UserControl>


MainPage를 실행하면 두개의 컬럼으로 이뤄진 Grid 에 한쪽은 Blue, 한쪽은 Red로 실행 됩니다.


그럼, IBackground 를 상속받아 구현 된 두 UserCotnrol을 Changed 버튼을 클릭 했을 때, RedUserControl은 Blue로, BlueUserControl은 Red 로 바꿔 보도록 하겠습니다.
<MainPage.cs>
public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
    } 

    IBackground option;
    public void optionChange()
    {
        SolidColorBrush brush = new SolidColorBrush(Colors.Blue);
        option = this.redUserControl;
        option.Changed(brush);
 
        brush = new SolidColorBrush(Colors.Red);
        option = this.blueUserControl;
        option.Changed(brush);
    }
 
    private void OptionChangeButton_Click(object sender, RoutedEventArgs e)
    {
        this.optionChange();
    }
}

Changed 버튼을 클릭 시, optionChanged 메소드가 실행 됩니다.
optionChanged 메소드에서는 UserControl 을 IBackground 형의 option 으로 할당한 뒤 option 객체의 Changed 메소드를 실행하게 되죠.
두 UserControl은 IBackground 형을 상속받았기 때문에 IBakground 형의 option으로 형변환이 가능하며, IBackground 에서 정의 된 메소드를 반드시 구현 했기 때문에, option 객체에 노출 된 Changed 메소드를 실행하게 됩니다.


USB 포트를 비교했었는데요, USB 포트의 표준에 맞춰 설계 된 디바이스르 USB 포트에 연결하면,
운영체제에서 디바이스를 인식하고, Driver 를 자동으로 설치하거나, Driver 를 설치하도록 지시하게 됩니다.
이와 마찬가지로, MainPage 에서 발생한 이벤트이지만, IBackground 를 상속받은 클래스의 객체에서 직접 객체 자신의 메소드를 통해 객체 자신의 속성을 변경하게 됩니다. 이는 interface 를 상속 받고, 반드시 구현 된 메소드가 존재하기 때문이죠.

아~ 여기서 제가 Observer 를 포스팅할까 했던 이유 중 하나인데요. 바로, 서로 다른 객체간의 통신(넓은 의미로 ㅎ)을 가능하게 하는 방법 중 하나가 interface의 활용이 될 수 있습니다. :)

다음에는 이런 interface의 특징을 활용한 Observer 를 만드는 방법을 포스팅 해 보겠습니다.
Posted by Min-gu, Kim

티스토리 툴바