'2015/07'에 해당되는 글 9건

3D 처리에서 카메라가 주인공 캐릭터를 따라가게 하는 방법.

 

(1) 카메라가 주인공의 등을 쫒아가게 하는 방법

 

이 방법은 player를 카메라가 직접 쫒아간다. 카메라가 추적하는 루틴은 우선 추적할 대상인 target의 위치로부터 뒤쪽으로 dist만큼 물러서서 height만큼 높이를 올려주고 카메라를 target과 같은 각도로 회전시킨다.

 

 


만약 카메라의 위치를 계산하는 로직을 Update 함수에서 구현했다면 Player의 움직임이 완료되기 전에 카메라가 이동하면 떨림 현상이 발생한다. 따라서 카메라의 위치 계산은 이동 및 회전을 마친후에 실행해야 한다. 따라서 LateUpdate 함수에서 실행한다.


 



주요함수는..


1. Mathf.LerpAngle(a : float, b:float, t: float) : float


2. Quaternion.Euler(euler : Vector3) : Quaternion


Mathf.LerpAngle은 t시간 동안 a부터 b까지 변경되는 각도를 반환한다. 부드러운 회전을 구현할 때 사용하는 함수로 세 번째 인자에 사용된 dampRotate 변수 값을 조정하면 회전하는 감도를 조절할 수 있다.


유니티에서는 모든 객체의 회전을 쿼터니언을 이용해 처리한다.

transform.rotation = Quaternion.Eular(0,30,0);



나의생각!  즉 위코드에서는 player가 update문을 이용해 이동했을 때 lastupdate에서 mathf.lerpangle을 통해 카메라와 플레이어와의 y축 앵글각의 차이를 계산하고 카메라의 포지션을 플레이어의 등을 정확히 바라보게 하기위하여, Vector3.forward 값에 y축 앵글각의 차이를 곱하여 빼준다. 이렇게 방향벡터에 앵글각을 곱하면 벡터의 방향이 자동으로 플레이어의 등을 바라보게 바뀌기 때문이아닐까?



(2) unity 사이트를 뒤져보다가 발견한.. 예제에서 3D이지만 3인칭 시점을 유지하여 카메라 앵글을 유지하는 방법.(지금 프로젝트 적용)



이 코드에서 transform.position 는 


private Transform tr; 변수로 tr = GetComponet<Transform>();으로 받아왔던 현재 object에대한 Transform 로직을 변수를 선언하지 않고 바로 transform.position으로 접근한 것으로 보인다. 이렇게 해도 작동하는 이유는 스크립트를 어차피 3d object에 child로 넣기때문이 아닐까?  


또한 Vector3.Lerp 함수가 있는데 이는 링크 http://hyunity3d.tistory.com/257을 참조했다.


using UnityEngine;

using System.Collections;

 

public class LerpTest : MonoBehaviour {

    //시작위치 위치

    public Transform startMarker;

    public Transform endMarker;

 

    public float speed = 10.0F;

    private float startTime;

    private float journeyLength;

    void Start()

    {

        startTime = Time.time;

        journeyLength = Vector3.Distance(startMarker.position, endMarker.position);//시작과끝 위치 거리

    }

    void Update()

    {

        //두점 사이의 거리가 10일때

        float distCovered = (Time.time - startTime) * speed; //속력 v = m/s 1초에 10움직임 한프레임당 1움직인다고하면

        float fracJourney = distCovered / journeyLength;// 속력 / 길이 = m/s / m = 1/s 시간 fracJourney = 0.1f

        transform.position = Vector3.Lerp(startMarker.position, endMarker.position, fracJourney); //

    }

}


유니티 함수 Lerp에 대해서 알아봅시다.


Lerp는 두 벡터 사이에 시간에 따른 위치를 구하는 함수입니다.

만약 startMarker position 은 (-5,0,0 )이고 endMarker 은 (5,0,0) 일때

위의 fracJourney 가 0.5초 일때 정확히 0,0,0 을 반환하게 되며, 0.1초 일때는 두점 사이의 거리가 10이므로 1,0,0 값을 반환 하게 됩니다.

fracJourncey 가 1.0f 일때나 그 이상일때는 endMarker 의 위치(5,0,0)를 반환합니다.

만약 0.0f이거나 그 이하일때는 startMarker의 위치(-5,0,0)를 반환하게 됩니다.




즉 아래코드를 분석하면, 

Start()에서 플레이어에 대한 현재 카메라의 상대위치를 구하고 


FixedUpdate()에서 

플레이어 위치변경 후 카메라가 위치해야할 위치를 구한 후,

현재 카메라위치에서 Lerp 함수를 써서 smooting하게 움직이는 효과를 썼다고 볼 수 있다.

만약 transform.postion = targetCampos; 라고 정의하면 smooting하게 움직이지 않을 것 이다.

 


 


블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요

제네릭이란? Generic

클래스를 사용할 타입을 클래스를 디자인할 때 지정하는게 아니라 클래스를 사용할 때 지정한 후 사용하는 기술을 말한다.

 

 

[일반적으로 사용하는 클래스]

    class GenericTest
    {
        private Object data = null;
        public void Setdata(Object data){
            this.data = data;
        }
        public Object Getdata()
        {
            return this.data;
        }
    }

    class mainTest
    {
        public static void Main()
        {
            string str = "이런이런 제네릭테스트하려궁 ㅎ ~~~";
            GenericTest gt = new GenericTest();
            gt.Setdata(str);
            string str2 = (String)gt.Getdata();
            Console.WriteLine(str2);
        }
    }



T

- 지금은 몰라 타입 T

 

제네릭에서 사용된 T의 정확한 표현

- 형식매개변수(Type Parametor)




* generic method 예제

class 제네릭{

public void AA(T value) {
 console.write(value);
}
public void T[] createArray(int size, T initVaue)
{
 T[] arr = new T[size];
 for(int i =0; i(2007);
 AA("이런된장");
 AA(1.005);

 string[] arr = createArry(3, "안녕, 제네릭");
 console.writeline("arr.length : {0}", arr.length);

 foreach(string o in arr)
  {
   prn(o)
  }
}

}



** 제네릭 컬렉션

class generic 
{
  //Dictionery
 public static void testDictionary()
 {
 Dictionary dic = new Dictionary();    
 dic.add("txt","notepad.txt");
 dic.add("bmp","paint.exe");
 dic.add("mp3","foobar.exe");
 
 Console.WriteLine([Dictionary]);
 foreach(DicBasker DicBk in dic)
 {
  Console.Write("key={0} value={1}",DicBk.Key, DicBk.Value);
 }
  Console.WriteLine();
 }
 
 public static void testLinkedList()
 {
  LinkedList genLL = new LinkedList();
  genLL.AddLast("4등");
  genLL.Addfrist("1등");
  genLL.AddAfter(genLL.Find("1등"),"2등");
  genLL.AddBefore(gtnLL.Find("2등"),"3등");

  Console.WriteLine("[LinkedList]");
  foreach(string aa in genLL)
  {
   Console.WriteLine("value={0}",aa);
  }
  Console.WriteLine();
 }

 public static void List()
 {
  List genList = new List();
  
  genList.add("한국");
  genList.add("중국");
  genList.add("중국2");
  genList.Remove("중국2");

  Console.WriteLine("List");
  foreach(string aa in genList)
  {
   Console.write("{0}", aa); 
  }
  Console.WriteLine();
 }

 public static void testQueue()
 {
  Queue genQueue = new Queue();
  
  genQueue.Enqueue(1);
  genQueue.Enqueue(2);
  genQueue.Enqueue(3);

  Console.WriteLine("[GenericQueue]");
  for(int i =0; i < 3; i++)
  {
   Console.WriteLine("value = {0}",genQueue.Dequeue);  
  }
  Console.WriteLine();
 }

 public static void testStack()
 {
  Stack genStack = new Stack();
  
  genStack.Push(1);
  genStack.Push(2);
  genStack.Push(3);
  
  Console.WirteLink("[Stack]")  
  for(int i = 0; i < 3 ; i++)
  {
   Console.WriteLine("value = {0}",genStack.Pop); 
  }
  Console.WriteLine();
 }
 
 public static void main()
 {
  testDictionary();
  testLinkedList();
  List();
  testQueue();
  testStack();
 }
}





**generic class 테스트

class ARRAY4ALL
{
 private t[] arr; //제네릭배열선언
 public ARRAY4ALL(int size) //생성자
 {
  arr = new T[size]; //인스터스화
 } 
 public T this[int i]
 {
  get{return arr[i];}
  set{arr[i] = value;}
 }
 public System.Collections.IEnumerator GetEnumerator()
 {
  for(int i =0; i  nArr = new ARRAY4ALL(5);
  ARRAY4ALL dArr = new ARRAY4ALL(5);
  ARRAY4ALL strArr = new ARRAY4ALL(5);

  for(int i =0 ; i<5; i++)
  {
   nArr[i] = i+1;
   dArr[i] = i+.1;
   strArr[i] = "" + Convert.ToChar('A'+i);
  }
  Console.WriteLine("nArr");
  foreach(int aa in nArr)
   Console.Write(aa);
  Console.WriteLine("".PadLeft(20,'-')); 

  Console.WriteLine("dArr");
  foreach(int bb in dArr)
   Console.Write(bb);
  Console.WriteLine("".PadLeft(20,'-'));

  Console.WriteLine("strArr");
  foreach(int cc in strArr)
   Console.Write(cc);
  Console.WriteLine("".PadLeft(20,'-'));

 }
}




** generic 응용

namespace genericArray 
{
 struct NameTag
 {
  public N name; 
  public C classNumber;
 }
 public class STUDENT
 {
  private T[] arr;

  public STUDENT(int size)
  {
   arr = new T[size];
  }
  public T this[int i]
  {
  get{ return= arr[i];}
  set{arr[i] = value;}
  }
  public System.Collections.IEnumerator GetEnumerator()
  {
   for(int i = 0; i> arr 
   = new STUDENT>(3);
   
   Random rnd = new Random();
  
   NameTag tag1, tag2, tag3;
   tag1.name = "봉"
   tag1.classNumber = rnd.Next(1000,2000);
   tag2.name = "몽"
   tag2.classNumber = rnd.Next(1000,2000);
   tag3.name = "진"
   tag3.classNumber = rnd.Next(1000,2000);
   
   arr[0] = tag1;
   arr[1] = tag2;
   arr[2] = tag3;
  
   foreach(NameTago in arr)
   {
    Console.WirteLine("{0}{1}",o.name,o.classNumber);
   }
  }
 }
}

'프로그램 > c#' 카테고리의 다른 글

C#의 제네릭이란?  (0) 2015.07.09
블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요

좌표계 - 로컬 좌표계와 월드 좌표계

 

 

유니티의 Scene 뷰는 3차원 공간을 표현한다. 이 3차원 공간에서 바뀌지 않는 기준이 되는 좌표를 월드 좌표 또는 Global 좌표라고 한다. 반면 로컬 좌표는 해당 게임오브젝트의 고유좌표를 지칭한다.

따라서 Translae 함수의 기준좌표계를 Space.Self로 지정하면 로컬 좌표계를 기주능로 이동하며 Space.World로 지정하면 월드 좌표계를 기준으로 이동하게 된다.

 

 

유니티는 왼손 좌표계를 사용하며 왼손 엄지가 X, 검지가 Y, 중지가 Z축이다. Z축이 전진방향이므로 Vector3.forward는 Vector(0,0,1)과 같은 의미이며, Vector3.forward에 속도를 곱하면 해당 방향으로 크기를 가진 벡터가 된다.

 

블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요

 

함수명 

함수속성 

 Awake

 - 스크립트가 실행될 때 한 번만 호출하는 함수이다.

 - 주로 게임의 상태 값 또는 변수 초기화에 사용한다.

 - Start함수가 호출되기 전에 맨 먼저 호출된다.

 - 스크립트가 비활성화돼 있어도 실행된다.

 - Coroutine 사용 불가

 Start

 - Update 함수가 호출되기 전에 한 번만 호출된다.

 - 스크립트가 활성화돼 있어야 실행된다.

 - 다른 스크립트의 모든 Awake가 모두 다 실행된 이후에 실행된다.

 Update

 - 프레임마다 호출되는 함수로 주로 게임의 핵심 로직을 작성한다.

 - 스크립트가 활성화돼 있어야 실행된다.

 LateUpdate

 - 모든 Update 함수가 호출되고 나서 한 번씩 호출된다.

 - 순차적으로 실행해야 하는 로직에 사용한다.

 - 카메라 이동 로직에 주로 사용하는 함수다.

 - 스크립트가 활성화돼 있어야 실행된다.

 FixedUpdate

 - 주로 물리 엔진을 사용하는 경우에 일정 시간 간격으로 힘을 가할때 사용하는 함수다.

 - 발생하는 주기가 일정하다.

 OnEnable

 - 게임오브젝트 또는 스크립트가 활성화됐을 때 호출된다.

 - Event 연결 시 사용한다.

 - Coroutine 사용 불가.

 OnDisable

 - 게임오브젝트 또는 스크립트가 비활성화됐을 때 호출한다.

 - Event 연결을 종료할 때 사용한다.

 - Conroutine 사용불가.

 OnGUI

  - GUI 관련 함수를 사용할 때 사용한다.

 

 

 

Update 함수 - 

매 프레임마다 한 번씩 호출되는 함수로 항상 최적화에 주의해야 한다. 예를 들어 게임이 실행되는 디바이스가 30 프레임 일 경우 Update 함수는 1초에 30번 호출된다. 따라서 조금이라도 부하가 걸리는 함수의 사용은 자제해야 한다. 특히 이동 로직은 Transform 컴포넌트의 Position 속성을 조금씩 변경하는 것으로 매 프레임마다 Transform 컴포넌트에 접근하는 방식은 바람직하지 않다. 

 

따라서 Update 함수에서 접근해야 할 컴포넌트는 미리 Awake 함수나 Start함수에서 할당하고 Update에서 사용한다.

 

ex) 

void Start(){

   tr = GetComponent<Transform>();

}

 

Translate 함수 - 

 

게임 오브젝트를 이동시키는 함수이다.

다음과 같은 문법을 적용한다.

 

tr.Translate(이동할 방향 * 속도 * 전진, 후진변수 * Time.deltaTime, 기준좌표계)

 

이동할 방향 => vector3로 지정 ex) vector3.forward

속도 => float 변수로 지정.

 

Time.deltaTime이란 ? 이전 프레임부터 현재 프레임까지 걸린 시간 Update 함수안에서 Transform 컴포넌트를 이용해 이동하는 로직에는 반드시 deltaTime을 곱해야만 프레임레이트에 상관없이 지정속도로 이동한다.  Time.deltaTime을 곱하지 않으면 매 프레임마다 10Unit이동하고 Time.deltaTime을 곱하면 매 초마다 10Unit씩 이동한다.

 

 

 

 

 

아래는 출처 - http://test.unitystudy.net/bbs/board.php?bo_table=writings&wr_id=43

 

코루틴(Coroutine)이 뭔가요?

C 언어등에서 일반적으로 사용하는 함수는 시작할 때 진입하는 지점이 하나 존재하고 함수가 모두 실행되거나, return 구문에 의해서 종료되는 지점을 설정할 수 있습니다. 이러한 함수를 서브루틴(Subroutine)이라 부르는데, 코루틴은 이를 더 일반화한 개념으로 진입하는 지점까지 여러 개를 가질 수 있는 함수를 의미합니다. 개념적으로만 본다면 서브루틴도 코루틴의 한 종류라고 볼 수 있겠지요.

 

 

코루틴이 왜 필요한가요?

코루틴이 없어도 게임을 만드는데 지장은 없습니다. 하지만 유니티에서 코루틴을 잘 활용하면 높은 성능을 내는 스크립팅의 제작이 가능해지고, 읽기 쉬운 코드를 만들 수 있게 됩니다.

코루틴은 어떻게 사용하나요?

C# 언어를 기준으로, 코루틴은 언제나 아래와 같은 구문을 통해서 선언되어야 합니다.

IEnumerator 함수이름 (인자)

IEnumorator 는 우리말로 열거자라고 하는데, 데이터의 목록을 하나씩 넘겨줄 때 사용되는 인터페이스입니다. 이 열거자와 코루틴이 무슨 관계이길래 이를 활용하는지 설명드리면, 코루틴은 호출한 함수와 서로 상호작용하면서 진행하도록 설계되어 있습니다. ( 어느 분께서는 협동루틴이라고 번역하셨는데, 정말 적절한 번역입니다! ) 아래 그림과 같이 코루틴은 자신을 호출한 함수에 데이터를 하나 넘겨주고 쉽니다. 받은 측에서는 데이터를 받고 나서 처리한 후에 코루틴에게 다음 데이터를 달라고 깨웁니다. 쉬고 있던 코루틴은 일어나서 다시 데이터를 전달하고.. 이를 계속 반복하는 구조로 동작합니다. 이러한 작업에 적절한 인터페이스가 IEnumerator 이며, C#에서 코루틴을 사용할 때는 이를 사용하여야 합니다.

CoRoutine1.png

일반적으로 호출한 함수에게 데이터를 전달할 때, return 구문을 사용하게 되면 데이터를 전달하고 함수는 종료됩니다. 코루틴은 이와 다르게 데이터를 전달한 후에 자신은 대기하고 있어야 하는데, 이를 위해 C#에서는 yield return이라는 키워드를 제공하고 있으며 이를 사용하면 됩니다. 

유니티에서 코루틴과 상호작용하는 주체는 엔진입니다. 코루틴을 사용할 때는 C#의 언어적인 기능을 익혀서 모든 것을 해결한다라는 개념보다 스크립트에서 엔진이 가지는 기능을 활용하는 또 한가지의 방법이라는 관점으로 생각해주시면 더 잘 이해되지 않을까.. 생각해봅니다.  
아래 그림과 같이 스크립트에서 StartCoroutine 함수를 통해 코루틴을 구동하면 코루틴은 첫 번째 데이터를 엔진에 전달하고 이 데이터를 분석한 엔진은 내부 루프를 돌면서 필요한 때가 되면 다음 데이터를 전달하도록 코루틴을 다시 호출해줍니다.

CoRoutine3.png

이 때 엔진에 전달할 데이터가 정말 중요한데, 데이터들은 코루틴이 유니티 엔진에게 보내는 일종의 명령으로 생각하시면 조금 더 이해가 쉽습니다.  
엔진에게 전달하는 명령들의 공통된 성격은 “나는 쉬고 있을 테니, 유니티 너가 내 명령을 보고 알아서 처리하고 끝나면 나를 깨워달라.” 입니다. ( 스크립트가 완전 갑이 됬습니다...  ) 
엔진이 제공하는 데이터들과 이들이 수행하는 명령들은 다음과 같습니다. 
( 참고로 아래 표 이외의 데이터들은 엔진이 처리하지 않습니다. )

코루틴용 데이터

엔진이 수행하는 기능

yield return null

다음 프레임까지 대기

yield return new WaitForSeconds(float)

지정된 초 만큼 대기

yield return new WaitForFixedUpdate()

다음 물리 프레임까지 대기

yield return new WaitForEndOfFrame()

모든 렌더링작업이 끝날 때까지 대기

yield return StartCoRoutine(string)

다른 코루틴이 끝날 때까지 대기

yield return new WWW(string)

웹 통신 작업이 끝날 때까지 대기

yield return new AsyncOperation

비동기 작업이 끝날 때까지 대기 ( 씬로딩 )



코루틴은 왜 좋은가요?

첫 번째로 좋은 점은 성능입니다.
예를 들어 코루틴에서 yield return new WaitForSeconds(10)이라는 명령을 수행하면 코루틴은 유니티 엔진에게 WaitForSeconds(10)이라는 데이터를 보내고 쉬기 시작합니다. 유니티 엔진은 이를 받고 기록해두었다가 묵묵하게 자기 할 일을 진행 하면서 10초가 지나면 쉬고 있는 코루틴을 깨웁니다. 
코루틴이 없이 일반적으로 이를 구현 한다면 Update 구문에서 Time.deltaTime을 사용하여 매 프레임마다 시간을 더해서 10초가 지났는지 감지해야 하는데, 프레임의 평균 소요 시간이 0.01초라고 한다면, 아이러니하게 10초 동안 대기하기 위해 스크립트는 Update 함수를 1000번 호출해야 합니다. 코루틴을 사용하면 10초 동안 스크립트가 쉬는데 말이지요~ 
이러한 점 때문에 특히 모바일 기기에서 코루틴의 활용은 성능 향상에 큰 영향을 미칩니다.

두 번째로 좋은 점은 가독성입니다. 
코루틴을 사용하면 읽기 좋은 코드가 만들어집니다. 아래 예를 보면 1초를 대기한 후에 2초를 대기하는 작업을 수행하는 두 코드의 가독성에서 확연한 차이를 볼 수 있습니다.
( 참고로 Start 함수를 IEnumerator로 선언해주면 엔진이 자동으로 코루틴으로 실행해줍니다. )

public class UpdateTimer : MonoBehaviour {

    float accumulator = 0.0f;

    bool wait1Finished = false;

    bool wait2Finished = false;

    float waitTime1 = 1.0f;

    float waitTime2 = 2.0f;

    void Start() {

        Debug.Log ("Action Start!");

    }

    void Update () {

        accumulator += Time.deltaTime;

        if(!wait1Finished && !wait2Finished) {

            if(accumulator >= waitTime1) {

                Debug.Log ("Action1 End");

                wait1Finished = true;

                accumulator = 0.0f;  

            }

        } else if(wait1Finished) {

            if(accumulator >= waitTime2) {

                Debug.Log ("Action2 End");

                wait2Finished = true;

                accumulator = 0.0f;  

            }

        }

    }

}

public class CoRoutineTimer : MonoBehaviour {

    float waitTime1 = 1.0f;

    float waitTime2 = 2.0f;

   

    IEnumerator Start () {

        Debug.Log ("Action Start");

        yield return new WaitForSeconds(waitTime1);

        Debug.Log ("Action1 End");

        yield return new WaitForSeconds(waitTime2);

        Debug.Log ("Action2 End");

    }    

}

 



코루틴의 특징 및 활용

코루틴의 활용 방법은 아주 다양할 수 있지만, 저는 세 가지로 구분해봤습니다. 
가장 많이 사용되는 빈도 순으로 정리해봤습니다.

첫 번째로 코루틴에서 가장 많이 사용되는 데이터는 WaitForSeconds 명령 입니다. 
앞서서 살펴본 예제처럼 마치 멀티 쓰레드 프로그래밍의 Thread.Sleep 함수 처럼 원하는 시간만큼 잠들어 있을 수 있습니다. 하지만 더욱 좋은 점은 엔진과 동일한 싱글 쓰레드로 돌아가기 때문에, 멀티 쓰레드 프로그래밍의 여러운 부분인 자원 관리 및 컨텍스트 스위칭(Context Switching)과 같은 다양한 고려 사항들을 신경쓸 필요가 없습니다.

두 번째로 많이 사용하는 부분은 비동기 작업인데, 웹에서의 데이터 수신, 씬 로딩과 같은 오래 걸리는 작업을 엔진에게 넘기고, 자신은 진행상황을 점검하면서 UI 처리만 수행하는 형태로 사용합니다. 
아래와 같은 WWW 클래스가 대표적인 비동기 작업인데, 웹에서 데이터를 다운로드 받기 위해 스크립트는 엔진에게 www 클래스를 만들어 데이터를 받아올 URL과 함께 넘기고 자신은 쉽니다. 
이를 받은 엔진은 비동기 처리를 위한 쓰레드를 생성하고 네이티브 라이브러리를 사용하여 웹에 접속한 후 데이터를 다운로드하면서 스크립트로부터 넘겨 받은 www 객체에 직장 상사에게 보고하듯 현재 진행상황을 저장해둡니다. ( 이 때 클래스의 레퍼런스를 넘겼기 때문에 다른 스크립트에서 진행상황을 읽어들이는 것이 가능합니다. )
엔진에서 다운로드가 끝나면 www 객체에는 완료 상황보고와 함께 받은 데이터가 저장되며, 엔진은 스크립트를 깨웁니다. 스크립트에서는 곤히 자고 일어나 눈비비면서 이를 사용하면 됩니다. 
아래는 코루틴이 쉬는 동안 엔진은 열심히 다운로드 받고, Update 함수는 이를 보면서 열심히 진행상황을 로그로 남기는 예제입니다. 

public class WebBasic : MonoBehaviour {

 

    public string url;

    WWW www;

   

    bool isDownloading = false;

   

    IEnumerator Start()

    {

        www = new WWW(url);

        isDownloading = true;

        yield return www;

        isDownloading = false;

        Debug.Log ("Download Completed!");

    }

   

    void Update()

    {

        if(isDownloading)

            Debug.Log (www.progress);

    }

 

}


매 프레임마다 진행상황을 그리거나, Update 함수 자체를 쓰는게 부담스러우면 아래와 같이 UI만을 그리는 코루틴을 하나 더 발생시키는 것도 좋은 방법이 되겠지요. 

public class WebAdvanced : MonoBehaviour {

   

    public string url;

    WWW www;

   

    IEnumerator Start()

    {

        www = new WWW(url);

        StartCoroutine("CheckProgress");

        yield return www;

        Debug.Log ("Download Completed!");

    }

   

    IEnumerator CheckProgress()

    {

        Debug.Log (www.progress);

        while(!www.isDone)

        {

            yield return new WaitForSeconds(0.5f);

            Debug.Log (www.progress);

        }

    }

       

}


마지막으로 코루틴을 사용하여 작업을 원하는대로 순차적으로 수행할 수 있습니다. 
이 기능은 C#과 같은 고급 언어(Hight Level Language)에서 지원하는 리플렉션(Reflection)기능과 코루틴 기능이 혼합된 활용성에 있어서 백미(白眉)라고 할 수 있겠습니다. 
이의 활용 예를 위해, RPG 게임등에서 흔히 보이는 패턴인 A와 B와 중 하나를 선택하게 하는 다이얼로그를 띄운 후, 사용자의 결정에 따라 A를 선택하면 ActionA를 수행하고 B를 선택하면 ActionB를 수행하는 구문을 프로그래밍 한다고 가정해봅시다. 이의 순서도는 아래와 같겠지요. 

CoRoutine4.png


이를 코루틴으로 구현하기 위해, 먼저 모든 행동을 각각 코루틴으로 정의해줍시다. 
그리고 각 행동들의 순서를 제어하는 코루틴을 만들어주고, 코루틴 안에서 행동들이 순차적으로 실행되도록 yield return new StartCoroutine 구문으로 차례대로 지정해줍니다. 첫 번째 코루틴의 결과에 따라 다음 행동이 A가 될지 B가 될지 결정되므로, 첫 번째 결과값을 string 변수로 지정하고, 변수 값을 StartCoRoutine에 넣으면 실시간으로 결과에 따른 해당 코루틴이 실시간으로 실행됩니다. 
아래는 이를 구현한 예제 입니다. 

public class DialogExample : MonoBehaviour {

   

    bool showDialog = false;

    string answer = "";

   

    IEnumerator Start()

    {

        yield return StartCoroutine("ShowDialog");

        yield return StartCoroutine(answer);

    }

   

    IEnumerator ShowDialog()

    {

        showDialog = true;

        do

        {

            yield return null;

        } while(answer == "");

       

        showDialog = false;

    }

   

    IEnumerator ActionA()

    {

        Debug.Log ("Action A");

        yield return new WaitForSeconds(1f);

    }

 

    IEnumerator ActionB()

    {

        Debug.Log ("Action B");

        yield return new WaitForSeconds(2f);

    }

   

    void OnGUI()

    {

        if(showDialog)

        {

            if(GUI.Button(new Rect(10f, 10f, 100f, 20f), "A"))

            {

                answer = "ActionA";  

            } else if(GUI.Button(new Rect(10f, 50f, 100f, 20f), "B")) {

                answer = "ActionB";

            }

        }

    }

   

}


결과 값의 유효성을 강화하고 싶다면 아래와 같이 enum을 보조적으로 사용하는 것도 좋은 방법이 될 수 있습니다.

using UnityEngine;

using System.Collections;

 

public class DialogExampleEnum : MonoBehaviour {

 

    enum DialogActions {

        ShowDialog,

        ActionA,

        ActionB

    };

   

    bool showDialog = false;

    DialogActions action = DialogActions.ShowDialog;

   

    IEnumerator Start()

    {

        yield return StartCoroutine(action.ToString());

        yield return StartCoroutine(action.ToString());

    }

   

    IEnumerator ShowDialog()

    {

        showDialog = true;

        do

        {

            yield return null;

        } while(action == DialogActions.ShowDialog);

       

        showDialog = false;

    }

   

    IEnumerator ActionA()

    {

        Debug.Log ("Action A");

        yield return new WaitForSeconds(1f);

    }

 

    IEnumerator ActionB()

    {

        Debug.Log ("Action B");

        yield return new WaitForSeconds(2f);

    }

   

    void OnGUI()

    {

        if(showDialog)

        {

            if(GUI.Button(new Rect(10f, 10f, 100f, 20f), "A"))

            {

                action = DialogActions.ActionA;        

            } else if(GUI.Button(new Rect(10f, 50f, 100f, 20f), "B")) {

                action = DialogActions.ActionB;

            }

        }

    }

 

}


이러한 특징을 잘 활용하면 AI를 위한 유한상태기계(Finite State Machine)를 구현할 때에도 별도의 클래스를 만들지 않고 쉽게 코딩이 가능해집니다.

결론

코루틴의 기본적인 동작원리에서 부터 활용법까지 살펴봤는데, 여기서 언급한 이외에도 다른 유용한 활용 예제가 있을 것으로 생각됩니다. 궁금한 점이나 더 좋은 활용 방법에 대해서 댓글 달아주시면 감사하겠습니다. 
 
블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요

텍스처 - 텍스처란 3D 모델의 표면에 그려지는 이미지 파일을 지칭한다. 마네킹에 다양한 옷을 입히는 것처럼 3D모델에 텍스처를 입힌다고 생각.

 

머티리얼 - 텍스처는 3D 모델에 적용하기 위한 이미지 파일로 중간에 머티리얼이라는 매개체가 필요하다. 텍스처를 '어떤 간격으로 반복하고 표면의 재질은 어떻게 표현하느냐' 등을 설정한다.

 

텍스처 적용 방식

 

1. 텍스처 임포트하고 project뷰의 텍스처를 scene뷰의 해당 모델로 드래그 앤 드롭.. - 권장x

 

2. 텍스처를 관리하는 상위 폴더를 만들고 하위에 materials폴더를 만들어 관리한다.

 

셰이더 - 셰이더는 머티리얼에 적용한 텍스처를 렌더링할 때 표면의 재질감을 표현하는 방법을 결정.

 

프리팹 - 자주 사용하는 객체를 미리 부품처럼 만들고 재사용할 수 있게 하는 것이다.

 

 

Tip!  Material을 무료 다운 받아 3D Object에 적용시킨 후 프리팹을 만들어 관리하는 것이 편하다. 하나의 프리팹을 수정하면 복사본에 모두 적용되기 때문이다.

 

skybox 의 적용! --> material에 shader의 skybox의 속성에 배치한다.

 

 

 

 

 

블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요

high gui

프로그램/opencv 2015.07.08 16:43

1. 이식성있는 그래픽 툴킷


openCV에서 카메라와 같은 하드웨어, 또는 파일 시스템, 운영체제 등과 연관된 작업을 지원하는 함수들은 모두 HighGUI라는 이름의 라이브러리에 포함되어 있다..




2. 윈도우


cvNamedWindow() - 윈도우 생성

cvDestroyWindow() - 윈도우 제거 


3. 영상 불러오기


IplImage* cvLoadImage(const char* filename, int iscolor) - 영상 불러오기.


int cvSaveImage(const char* filename, const CvArr* image) - 영상 저장.



4. 화면에 영상 출력


cvShowImage(const char* name, const CvArr* image);



추후작성.



'프로그램 > opencv' 카테고리의 다른 글

high gui  (0) 2015.07.08
opencv 기초  (2) 2015.07.03
블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요

 

 

 

 

4-1 기분이 좋다.ㅋㅋ

'사는이야기 > 끄적끄적' 카테고리의 다른 글

4-1 성적  (0) 2015.07.06
블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요

1일차. 교육을 받고 자리를 배정받았다.

 

가서 느끼니 프로젝트를 성공적으로 마무리하고 싶다는 욕구가 솟는다.

열심히 하지말고 잘하라고 하신다. 1등으로 마무리해보자.

 

'활동사항 > 넷마블R&D인턴(2015-07-06~2015-08-27)' 카테고리의 다른 글

기술 appendix  (0) 2015.10.04
1일차  (0) 2015.07.06
블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요


* 스칼라 벡터에 대한 구조체 정의


 cvPoint

 int x,y

 영상 내 한 점의 위치

 cvPoint2D32f

 float x,y

 2차원 실수 공간에서의 좌표

 cvPoint3D32f

 float x,y,z

 3차원 실수 공간에서의 좌표

 cvSize

 int width,height

 영상의 크기

 cvScalar

 double val[4]

 RGB값


openCV는 주로 IplImage 데이터 타입을 사용한다. IplImage는 '영상'이라고 부르는 것을 표현하는 데이터 타입이다. 


이러한 IplImage 데이터타입은 cvArr로 부터 상속된 구조를 갖는다 .cvArr->cvMat->IplImage


OpenCV에서 행렬은 32비트 실수형 1채널(CV_32FC1), 부호없는 8비트 정수형 3채널(CV_8UC3) 등 여러 데이터 타입으로 구성될 수 있다. 


이러한 행렬을 만드는 함수는 cvCreateMat(int rows,int cols,int type)을 쓴다. 


행렬을 만들고 나서 행렬의 속성을 알아보기 위해서 cvGetElemType(), cvGetDims(), cvGetDimSize()등의 함수를 사용한다.



* 행렬 데이터 접근하기 


1. 간편한 방법


CV_MAT_ELEM() 과 CV_MAT_ELEM_PTR() 매크로를 이용한다 .


2. 엄격한 방법


cvPtr*D 또는 cvGet*D 형태의 함수를 사용한다.


ex)  uchar* cvPtr1D(const CvArr* arr,int idx0, int* type=NULL);

ex)  double cvGetReal1D(const CvArr* arr,int idx0);


cvGet*D와 유사한 cvSet*D 형태의 함수는 행렬 또는 영상의 특정 위치 원소값을 설정하는 함수이다.


ex) void cvSetReal1D(CvArr* arr, int idx0, double value);

    void cvSetReal2D(CvArr* arr, int idx0, int idx1, double value);


3.적절한 방법


앞서 행렬 데이터를 조작하는 다양한 접근 함수를 알아보았지만 거의 사용하지 않는다.

보통 행렬의 데이터를 접근할 때, 포인터를 통한 접근을 우선시한다.


float sum(const CvMat* mat)

{

float s = 0.0f;


for(int row=0;row<mat->rows;row++)

{

const float* ptr(const float*)(mat->data.ptr+row*mat->step);


for(col=0;col<mat->cols;col++)

s+=*ptr++;

}


return(s);

}



IplImage 구조체 


중요변수 -> width, height, depth, nChannels, origin, dataOrder,widthStep


nChannels ->픽셀의 채널 수. 

depth -> 데이터의 타입.

origin -> IPL_ORIGIN_TL(0), IPL_ORIGIN_BL(1) 중 하나를 가질 수 있다. 영상에서 좌표의 원점이 좌상단에 위치하는 지 좌하단에 위치하는지를 가리킨다. 

dataOrder  -> IPL_DATA_ORDER_PIXEL 또는 IPL_DATA_ORDER_PLANE 중에 하나를 지정한다 . 이 값이 IPL_DATA_ORDER_PIXEL이면 다중채널값을 차례대로 가지고 있고 , 일반적인 인터리브 방식이다 . 만약 dataOrder가 IPL_DATA_ORDER_PLANE값을 가지면 각 채널 별로 따로 픽셀값이 저장된다.(일반적으로 인터리브 방식사용한다. 행의 개수 = 영상의 높이, 각 행에서는 각 채널값이 교차된 순서로 나타난다.)

widthStep -> 한 행이 실제로 차지하고 있는 바이트 수.

imageData -> 영상 데이터의 시작 주소를 가리킨다. 


#관심영역(roi)  --> 영상 내부에 roi가 설정되어 있을 경우, 영상을 다루는 함수는 영상 전체에 대하여 동작하는 대신 roi로 설정된 영역에서만 동작한다. 모든 opencv 함수는 roi 설정 여부를 스스로 판단하여 동작한다 .



* 영상데이터 접근하기


OpenCV 라이브러리가 영상 처리와 관련된 다양한 함수들을 제공하지만 실제 작업에서 필요한 모든 함수들을 완벽하게 제공할 수는 없다. 예를 들어, 3채널 HSV 영상에서 색상값은 그대로 둔 채 채도와 명도값을 모두 255로 설정하는 작업은 행렬을 이용하여 영상자체를 가리키는 포인터를 이용하여 수행하는것이 효과적이다.


HSV 영상에서 "S"와 "V"에 해당하는 값을 최대화


void saturate_sv(IplImage* img)

{

for(int y=0;y<img->height;y++)

{

uchar* ptr = (uchar*)(img->imageData + y*img->widthStep);


for(int x=0;x<img->width;x++)

{

//ptr [3*x]는 h값을 나타낸다.

ptr[3*x+1] = 255;

ptr[3*x+2] = 255;

}

}



* roi를 설정하는 두가지 방법.


1. void cvSetImageROI(IplImage* image,CvRect rect);

   void cvResetImageROI(IplImage* image);


#include 
#include 

int main()
{
      IplImage* src = cvLoadImage("~~",1);

      cvSetImageROI(src, cvRect(x,y,width,height));
      cvAddS(src, cvScalar(add),src);
      cvRestImageROI(src);

      cvNamedWindow("Roi_Add", 1);
      cvShowIamge("Roi_Add", src);
      cvWaitKey();

      cvReleaseImage(&src);
      cvDestroyWindow("Roi_Add");
}
      return 0;
}
      

2. widthStep을 이용하여 관심 영역을 처리하는 대체방법

IplImage *sub_img = cvCreateImageHeader(
     cvSize(
           interest_rect.width,
           interest_rect.height
     ),
     interest_img->depth,
     interest_img->nChannels
);

sub_img->origin = interest_img->origin;

sub_img->widthStep = interest_img->widthStep;

sub_img->imageData = interest_img->imageData + interest_rect.y * interest_imag->widthStep + interest_rect.x * interest_img->nChannels;

cvAddS(sub_img,cvScalar(1),sub_img);

이하 다양한 함수들은 생략하고 필요할 때마다 참조하여 공부한다

아래는 연습문제 예제이다. 개인적으로 2,4,5,6,7 번이 중요하다고 생각한다.

#include

#include #include typedef struct my_struct{ int a; CvPoint b; CvRect c; }my_struct; void write_my_struct(CvFileStorage* fs, const char* name, my_struct *ms); void read_my_struct(CvFileStorage* fs, my_struct *ms); int main() { /01번 CvMat* mat = cvCreateMat(1,1,CV_32FC3); CvMat* dst = cvCreateMat(1,1,CV_32FC3); *((float*)CV_MAT_ELEM_PTR(*mat, 0, 0)) = 0; //a cvAbs(mat, dst); //b CvMat* noise = cvCreateMat(10, 1, CV_64FC1); CvRNG rng[] = { cvRNG(time(NULL)), cvRNG(time(NULL)) }; cvRandArr(rng, noise, CV_RAND_NORMAL, cvScalarAll(0), cvScalarAll(0)); //c,d CvPoint((int)CvPoint2D32f(3.0, 2.0).x,(int)CvPoint2D32f(3.0, 2.0).y); cvPoint2D32f((float)cvPoint(3,2).x,(float)cvPoint(3,2).y); / /02번 - 100*100 3채널 2차원 행렬 생성후 원그리고 출력. CvMat* mat = cvCreateMat(100,100,CV_8UC3); cvSet(mat, cvScalarAll(255)); cvCircle(mat, cvPoint(50, 50), 40, CV_RGB(0, 0, 250)); cvNamedWindow("Drawing Graphics", CV_WINDOW_AUTOSIZE); cvShowImage("Drawing Graphics", mat); cvWaitKey(0); / /04번- 100*100 3채널 RGB영상을 생성하고 흰색초기화후 포인터 연산을 통한 녹색사각형 출력 IplImage* src = cvCreateImage(cvSize(100, 100), , 3);; cvSet(src, cvScalarAll(255)); for (int i = 20; i < 40; i++){ uchar* ptr = (uchar*)(src->imageData+i*src->widthStep); for (int j = 5; j < 20; j++){ ptr[3 * j] = 0; ptr[3*j+1] = 150; ptr[3 * j + 2] = 0; } } cvNamedWindow("Drawing Graphics", CV_WINDOW_AUTOSIZE); cvShowImage("Drawing Graphics", src); cvWaitKey(0); / /05번 - 관심영역연습. IplImage* src = cvCreateImage(cvSize(210, 210), IPL_DEPTH_8U, 1); cvSet(src, cvScalarAll(0)); for (int i = 0; i < 110; i += 10){ cvSetImageROI(src, cvRect(i, i, 210-2*i, 210 - 2 * i)); cvSet(src, cvScalarAll(2 * i)); } cvResetImageROI(src); cvNamedWindow("5번", 1); cvShowImage("5번", src); cvWaitKey(); cvReleaseImage(&src); cvDestroyWindow("5번"); / /6번 - cvNot 활용. IplImage* src = cvLoadImage("C:/Users/Jonghwan123/Documents/카카오톡 받은 파일/opencv.jpg"); IplImage* dst1 = cvCreateImageHeader(cvSize(20,30), src->depth, src->nChannels); IplImage* dst2 = cvCreateImageHeader(cvSize(20,30), src->depth, src->nChannels); dst1->origin = src->origin; dst2->origin = src->origin; dst1->widthStep = src->widthStep; dst2->widthStep = src->widthStep; dst1->imageData = src->imageData + 10*src->widthStep + 10*src->nChannels; dst2->imageData = src->imageData + 60*src->widthStep + 50 *src->nChannels; cvNot(dst1,dst1); cvNot(dst2,dst2); cvNamedWindow("6번", 1); cvShowImage("6번", src); cvWaitKey(); / /7번 - 임계값을 구하고 임계값을 통한 cvCmp cvSubS 활용. IplImage* src = cvLoadImage("C:/Users/Jonghwan123/Documents/카카오톡 받은 파일/opencv.jpg"); IplImage* dst1 = cvCreateImage(cvSize(src->width,src->height),src->depth,1); IplImage* dst2 = cvCreateImage(cvSize(src->width,src->height),src->depth,1); IplImage* dst3 = cvCreateImage(cvSize(src->width,src->height),src->depth,1); cvSplit(src, dst1, dst2, dst3, NULL); IplImage* clone1 = cvCreateImage(cvSize(dst1->width, dst1->height), dst1->depth, 1); IplImage* clone2 = cvCreateImage(cvSize(dst1->width, dst1->height), dst1->depth, 1); double min = 0; double max = 0; cvMinMaxLoc(dst1, &min, &max, NULL, NULL, NULL); unsigned char thresh = (unsigned char)((max + min) / 2.0)+100; cvSet(clone1, cvScalar(thresh)); cvSet(clone2, cvScalar(0)); cvCmp(dst1, clone1, clone2, CV_CMP_GE); cvSubS(dst1, thresh / 2, dst1, clone2); cvNamedWindow("7번", 1); cvShowImage("7번", clone2); cvWaitKey(); / /8번 my_struct ms = { 1, { 20, 30 }, { 20, 30, 10, 20 } }; //CvFileStorage* wfs = cvOpenFileStorage("cfg.xml", 0, CV_STORAGE_WRITE); CvFileStorage* rfs = cvOpenFileStorage("cfg.xml", 0,CV_STORAGE_READ); //write_my_struct(wfs, "jonghwan",&ms); //CvSeq* s = cvGetFileNodeByName(rfs, 0, "jonghwan")->data.seq; //CvFileNode* filenode = (CvFileNode*)cvGetSeqElem(s,0); read_my_struct(rfs, &ms); / } //8번함수1 void write_my_struct(CvFileStorage* fs, const char* name, my_struct *ms){ cvWriteInt(fs, "a", ms->a); cvStartWriteStruct(fs, "b", CV_NODE_SEQ); cvWriteInt(fs, 0, ms->b.x); cvWriteInt(fs, 0, ms->b.y); cvEndWriteStruct(fs); cvStartWriteStruct(fs, "c", CV_NODE_SEQ); cvWriteInt(fs, 0, ms->c.x); cvWriteInt(fs, 0, ms->c.y); cvWriteInt(fs, 0, ms->c.width); cvWriteInt(fs, 0, ms->c.height); cvEndWriteStruct(fs); cvReleaseFileStorage(&fs); //cvWrite(fs, "b", &ms->b); //cvWrite(fs, "c", &ms->c); } //8번함수2 void read_my_struct(CvFileStorage* fs, my_struct *ms){ int a = cvReadIntByName(fs, 0, "a", 5); CvSeq* b = cvGetFileNodeByName(fs, 0, "b")->data.seq; int bx = cvReadInt((CvFileNode*)cvGetSeqElem(b, 0)); int by = cvReadInt((CvFileNode*)cvGetSeqElem(b, 1)); CvSeq* c = cvGetFileNodeByName(fs, 0, "c")->data.seq; int cx = cvReadInt((CvFileNode*)cvGetSeqElem(c, 0)); int cy = cvReadInt((CvFileNode*)cvGetSeqElem(c, 1)); int width = cvReadInt((CvFileNode*)cvGetSeqElem(c, 2)); int height = cvReadInt((CvFileNode*)cvGetSeqElem(c, 3)); //printf("%d", frame_count); }


'프로그램 > opencv' 카테고리의 다른 글

high gui  (0) 2015.07.08
opencv 기초  (2) 2015.07.03
블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요