yield 문과 Iterator에 대해서

C#/Story|2019. 9. 16. 17:29
IEnumrable<int> Range(int start,int end) {
    for (int i=start;i<end;i++) {
    	yield return i;
    }
}

이런식으로 yield return 같은 방법으로 값을 반환하는데 쓴다.

yield 키워드가 없었다면 이 함수는 항상 start(int)값을 반환하는 함수가 되었겠지만

yield 문은 함수가 어디까지 실행되었는지를 기억해서 그 이후부터 실행하기 때문에

이 함수는 start부터 end-1값까지의 이터레이터를 반환하는 함수가 된다.

그럼 이터레이터를 반환하지않고 그냥 리스트를 반환하면 되지라고 생각할 수도 있으나 

그 차이를 알아보기 위해 위 함수와 같은 행동을 하는 RangeInList 작성해보았다. 

IEnumrable<int> RangeInEnumerable(int start,int end) {
    for (int i=start;i<end;i++) {
    	Console.Write(i);
    	yield return i;
    }
}

List<int> RangeInList(int start,int end) {
    var list=new List<int>();
    for (int i=start;i<end;i++) {
    	Console.Write(i);
    	list.Add(i);
    }
    return list;
}

static void Main() {
    RangeInEnumerable(0,10000).Take(5).Foreach(x=>{
    	//Action
    });//Console Result: 01234
    
    RangeInList(0,10000).Take(5).Foreach(x=>{
    	//Action
    });//Console Result: 012345....9999 
}

이터레이터를 반환하는 함수는 해당 Element가 필요할 때 까지 지연했다가 쿼리가 끝날때 한번만 순회한다.

그러므로 Linq와 같은 쿼리구문과 궁합이 매우 좋고 시퀀스를 처리하는데 매우 적합하다.

'C# > Story' 카테고리의 다른 글

ObservableCollection에 대해서  (0) 2019.11.02
2차원 배열과 Matrix Class 제작  (0) 2019.09.23

댓글()

유니티 이동 컴포넌트 (Mover)

C#/Unity|2019. 9. 14. 18:46

일반적으로 유니티에서 게임오브젝트를 이동시키는 방법은 Rigidbody를 이용한 물리 법칙을 통한 계산이나 Transform에 관련된 함수를 사용하는 것이다. 이는 Platformer 장르나 물리법칙이 적용된 게임에는 적합하나 RTS나 Defense장르에서 사용하기는 부적합하다. 그래서 목표지까지 이동이나 다른 GameObject를 따라가는 로직을 구현하기 위해 컴포넌트를 제작했다.

ToGoal함수를 이용해 목표지(Vector)까지 이동가능하며 ToFollow함수를 이용해 적을 따라가게 할 수 있다.

[DisallowMultipleComponent]
public class Mover : MonoBehaviour {
    public enum MoveType {
        None, Goal, Follow
    }
    public enum EndEvent {
        None, DestoryGameObject, DestroyComponent, DeactiveGameObject
    }

    public float speed;
    public float stopDistance = 0.1f;
    public MoveType moveType;
    public EndEvent endEvent;

    public Vector3 destination;
    public GameObject target;

    private List<Vector3> nextGoals = new List<Vector3>();
    private event Action OnCompleted;
    private event Action OnFailed;


    public bool IsMoving() {
        return moveType != MoveType.None;
    }

    public Mover SetSpeed(float _speed) {
        speed = _speed;
        return this;
    }
    public Mover SetEndEvent(EndEvent _endEvent) {
        endEvent = _endEvent;
        return this;
    }

    public void ToGoal(Vector3 _destination, Action completeAct = null) {
        destination = _destination;
        OnCompleted += completeAct;
        moveType = MoveType.Goal;
    }

    public void ToGoal(List<Vector3> desList, Action completeAct = null) {
        if(desList.Count == 0)
            return;
        ToGoal(desList[0], completeAct);
        desList.RemoveAt(0);
        nextGoals = desList;
    }

    public void ToFollow(GameObject _target) {
        ToFollow(_target, null, null);
    }
    public void ToFollow(GameObject _target, Action completeAct) {
        ToFollow(_target, completeAct, null);
    }
    public void ToFollow(GameObject _target, Action completeAct, Action failAct) {
        OnCompleted += completeAct;
        OnFailed += failAct;
        target = _target;
        moveType = MoveType.Follow;
    }



    public void Stop() {
        ResetState();
    }

    private void Update() {
        if(moveType == MoveType.None)
            return;

        if(moveType == MoveType.Goal) {//목표지점까지 이동
            transform.position = Vector3.MoveTowards(transform.position, destination, speed * Time.deltaTime);

            if(Vector3.Distance(transform.position, destination) < stopDistance) {
                if(nextGoals.Count == 0) {
                    transform.position = destination;
                    OnCompleted?.Invoke();
                    ResetState();
                    ExcuteEndEvent();
                    return;
                } else {
                    destination = nextGoals[0];
                    nextGoals.RemoveAt(0);
                    return;
                }
            }
        } else if(moveType == MoveType.Follow) {//목표 타겟한테 이동
            if(target == null) {
                OnFailed?.Invoke();
                ResetState();
                ExcuteEndEvent();
                return;
            }
            transform.position = Vector3.MoveTowards(transform.position, target.transform.position, speed * Time.deltaTime);
            if(Vector3.Distance(transform.position, target.transform.position) < stopDistance) {
                transform.position = target.transform.position;
                OnCompleted?.Invoke();
                ResetState();
                ExcuteEndEvent();
            }
        }
    }


    private void ResetState() {
        moveType = MoveType.None;
        OnFailed = null;
        OnCompleted = null;
    }

    private void ExcuteEndEvent() {
        if(endEvent == EndEvent.DestroyComponent) {
            Destroy(this);
        } else if(endEvent == EndEvent.DestoryGameObject) {
            Destroy(gameObject);
        } else if(endEvent == EndEvent.DeactiveGameObject) {
            gameObject.SetActive(false);
        }
    }
}

'C# > Unity' 카테고리의 다른 글

유용한 Unity & C# 관련 링크  (0) 2020.01.19
유니티 클릭 컴포넌트 (Clickable)  (0) 2019.09.14
유니티 Android native 사운드 적용  (0) 2019.09.14

댓글()

유니티 클릭 컴포넌트 (Clickable)

C#/Unity|2019. 9. 14. 18:32

유니티 게임오브젝트의 클릭은 OnMouseUp이나 OnMouseDown같은 콜백함수로 구현되어있다.

하지만 Double Click이나 Long Click같은 로직은 코루틴을 이용해서 구현해야 하는데 

쉽게 사용하기 위해 컴포넌트 방식으로 구현해 보았다. (2D 기반이며 Collider가 있어야한다)

내부로직은 코루틴으로 작동하며 event를 등록하거나 Regist함수를 호출함으로서 사용가능하다.

개선사항은 Clickable 컴포넌트간의 우선순위 설정이 필요할 수도 있겠다. 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[DisallowMultipleComponent]
[RequireComponent(typeof(Collider2D))]
public class Clickable : MonoBehaviour {
    public static float DOUBLE_INTERVAL=0.2f;
    public static float LONG_INTERVAL=0.8f;

    public bool isOnce = true;
    public bool isDouble;
    public bool isLong;

    public event Action OnOnceClicked;
    public event Action OnDoubleClicked;
    public event Action OnLongClicked;

    private int downCount;
    private float clickTime;

    private Coroutine doubleCoroutine;
    private Coroutine longCoroutine;


    public Clickable RegistInOnce(Action act) {
        isOnce = true;
        OnOnceClicked += act;
        return this;
    }
    public Clickable RegistInDouble(Action act) {
        isDouble = true;
        OnDoubleClicked += act;
        return this;
    }
    public Clickable RegistInLong(Action act) {
        isLong = true;
        OnLongClicked += act;
        return this;
    }
    public Clickable ClearInOnce(Action act) {
        OnOnceClicked = null;
        return this;
    }
    public Clickable ClearInDouble(Action act) {
        OnDoubleClicked = null;
        return this;
    }
    public Clickable ClearInLong(Action act) {
        OnLongClicked = null;
        return this;
    }

    private void OnMouseDrag() {
        clickTime += Time.deltaTime;
    }
    private void OnMouseUp() {
        clickTime = 0f;
    }

    private void OnMouseDown() {
        clickTime = 0f;
        downCount++;

        if(isOnce && !isDouble && !isLong) {
            OnOnceClicked?.Invoke();
            return;
        }
        if(isDouble && doubleCoroutine == null) {
            doubleCoroutine = StartCoroutine(CoWaitDouble());
        }
        if(isLong && longCoroutine == null) {
            longCoroutine = StartCoroutine(CoWaitLong());
        }
    }

    private IEnumerator CoWaitDouble() {
        float startTime = Time.time;
        int curCount = downCount;
        while(true) {
            if(curCount < downCount) {
                OnDoubleClicked?.Invoke();
                break;
            }

            if(startTime + DOUBLE_INTERVAL < Time.time) {
                if(longCoroutine == null) {
                    OnOnceClicked?.Invoke();
                }
                break;
            }
            yield return null;
        }
        doubleCoroutine = null;
    }
    private IEnumerator CoWaitLong() {
        float startTime = Time.time;
        while(true) {
            if(!Input.GetMouseButton(0)) {
            	if (startTime+DOUBLE_INTERVAL<Time.time) {
                    OnOnceClicked?.Invoke();
                }
                break;
            }

            if(startTime + LONG_INTERVAL < Time.time) {
                OnLongClicked?.Invoke();
                break;
            }

            yield return null;
        }
        longCoroutine = null;
    }
}

'C# > Unity' 카테고리의 다른 글

유용한 Unity & C# 관련 링크  (0) 2020.01.19
유니티 이동 컴포넌트 (Mover)  (0) 2019.09.14
유니티 Android native 사운드 적용  (0) 2019.09.14

댓글()