상세 컨텐츠

본문 제목

[유니티 소소모임] 1주차. 유니티 기초 (Editor: 니나노)

24-25/Untiy

by 롱롱😋 2024. 10. 4. 12:03

본문

728x90

 

 

 

1. 유니티 LifeCycle 

초기화 -> 물리연산 -> 게임 로직 -> 해체

1-1. Awake()

게임 오브젝트 생성할 때, 최초 1번만 실행되는 함수이다.

1-2. Start() 

Update() 함수 시작 전에 최초 1번 실행되는 함수이다.

* Awake()와 Start()는 초기화의 영역

1-3.  FixedUpdate()

유니티 엔진이 물리연산을 하기 전에 실행되는 업데이트 함수이다.

컴퓨터의 사양과 관계 없이 고정된 실행주기로 호출되며 CPU 부하가 올 수 있다.

*FixedUpdate()는 물리 연산의 영역

1-4. Update()

주기적으로 변하는 게임 로직을 적용하는 함수이다.

환경(=성능)에 따라 실행주기가 달라질 수 있다.

*Update()는 게임로직의 영역

1-5. LateUpdate()

모든 업데이트가 끝난 뒤 마지막으로 호출되는 함수이다.

1-6. OnDestroy()

오브젝트가 삭제될 때 무언가를 남기고 삭제되는 함수이다.

*OnDestory는 해체의 영역

1-7. OnEnable

(활성화 Awake()와 Start()사이에 위치)

최초 1회 실행이 아닌 크고 켜고 할 때마다 실행된다.

1-8.OnDisable

(Lateupdate()와 OnDesytoy 사이에 위치)

 모든 업데이트가 끝나고 오브젝트가 비활성화 되거나 삭제될 때 실행된다.

 

 

2. Time.deltaTime 

Update(): 매 프레임마다 함수 안의 내용을 동일하게 계속 호출한다.

Update()는 기본적으로 1초에 프레임을 호출하는 횟수(=성능 차이)가 다르게 될 경우 문제가 발생하며, 이 문제를 해결하기 위해서 Time.deltaTime을 사용한다.

 

2-1. Time.deltaTime

Update() 함수가 호출되는 간격을 알려준다.

 

▷ 1초에 10프레임인 경우

Time.deltaTime = 0.1f (1초 / 10 프레임이라는 뜻) 

=> Update()함수가 10번 호출 된다. 0.1f * 10(프레임) = 1f

 

▷ 1초에 20프레임인 경우

Time.deltaTime = 0.05f (1초 / 20 프레임이라는 뜻)

=> Update()함수가 20번 호출 된다. 0.05f * 20(프레임) = 1f 

 

결론적으로 모두 1f 이다.

=> Time.deltaTime을 통해 각 컴퓨터 속도 및 성능에 맞추어 모든 사용자의  PC에서 동일한 퍼포먼스를 보여주도록 한다.

 

 

3. Lerp 

유니티에서 무언가 이동하거나 자연스럽게 화면 전환하거나 기타 등등에 이용한다.

3-1. 선형 보간법

끝점의 값이 주어졌을 때 그 사이에 위치한 값을 추정하기 위해서 직선 거리에 따라 선형적으로 계산하는 방법이다.

 

Lerp(float a, float b, float t)

a: 시작 점
b: 끝 점
t: a와 b 사이의 비율 지점으로 0에서 1 사이의 값을 가진다.
ex) t가 0이면 시작점인 a, t가 1이면 끝점인 b, 0.5이면 그 중간 점이다.

 

public class Lerp : MonoBehaviour
{
    Vector3 startPos; // 시작 값
    Vector3 targetPod = new Vector3(0, 5, 0); // 끝 값

    float currentTime = 0; //a와 b를 선형보간하는 t
    public float moveTime = 5.0f; //움직이는 시간

    private void Start()
    {
        startPos = transform.position;
    }
    void Update()
    {
        currentTime += Time.deltaTime; // 매 프레임마다 업데이트
        if(

        transform.position = Vector3.Lerp(startPos, targetPod, currentTime / moveTime);
    }
}

 

=> 특정 목표 위치까지의 이동에 사용한다.

 

 

4.DOTween 

  • Tween: 오브젝트의 시간당 변화
  • DOTween: 오브젝트의 애니메이션 혹은 특정 값의 부드러운 변화를 위해 제공해주는 에셋

4-1. Do... Set... ON...

public class DotweenTest2 : MonoBehaviour
{
    private Vector3 targetPos = new Vector3(0,5,0);
    private Vector3 targetScale = new Vector3(2,2,2);
    
    Tweener tr;

    Sequence sequence; // 하나가 아닌 다른 Tweener들을 제어

    void Start()
    {
        // 기본 설정 값임
        DOTween.Init(false, true, LogBehaviour.Verbose).SetCapacity(200, 50);
        // 첫번째 요소 autoKillMode: Dotween 재사용 여부 결정
        // useSafeMode: 약간 느리지만 실행되는 동안, 실행 대상이 파괴 되는 경우와 같은 예외 상황을 자동으로 처리
        // LogBehavior: 오류 메세지 기록을 설정
        // SetCapacity(Tweener 개수, Sequence 개수)

        transform.DOMove(targetPos, 1.0f); // endValue, duration
        transform.DOMoveX(10f, 1.0f);
        transform.DOShakePosition(10f, 1, 10, 90, true);
        transform.DOScale(targetPos, 2.0f);
        transform.DOScale(1.5f, 1.0f).SetLoops(3, LoopType.Restart); // set~ 변화에 대한 설정
        transform.DOScale(1.5f, 1.0f).SetLoops(3, LoopType.Yoyo);
        transform.DOScale(1.5f, 1.0f).SetDelay(1.0f);

        Tweener tr = transform
            .DOScale(1.5f, 2.0f)
            .SetEase(Ease.InBounce)
            .OnComplete(()=> transform.DOMove(targetPos, 2.0f));

        tr.Play(); //이걸로 재사용 가능

        //tr.Kill(); // 재사용하던 tween 제거
        // Do 대상의 변화를 직접 지시
        // Set 추가 설정
        // on 람다를 이용한 콜백 함수 실행

        //Dotween의 반환 타입은 Tweener -> 값을 제어하고 애니메이션을 적용 -> 재사용 가능(첫번째 인자 false로 바꾸기 -> 메모리에 저장)
        // Sequence: 값을 제어하지 않고 다른 Tweener 를 제어, 그룹으로 애니메이션을 적용
    }
}

 

다양한 옵션들을 통해 오브젝트의 동작, 애니메이션을 줄 수 있다.

  • DOMove: 이동
  • DOShakePosition: 진동
  • DOScale: 크기 변화
  • DORotate: 회전
  • ...등등 많은 동작 제공
Tweener tr = transform
.DOScale(1.5f, 2.0f)
.SetEase(Ease.InBounce)
.OnComplete(()=> transform.DOMove(targetPos, 2.0f));
  1. DO(명령)을 통해서 대상의 변화를 직접 지시한다.
  2. Set을 통해서 추가로 변화에 대한 설정을 줄 수 있다.
  3. ON 옵션으로 람다를 이용한 콜백함수 설정도 가능하다.

오브젝트에게 DO를 통해 크기 변화를 지시했는데, set을 통해 동시에 Bounce 효과를 준다. 그리고 ON을 통해 complete, 즉 동작이 종료되면 이동하도록 지시했다.

 

4-2. Tweener

DOTween의 반환 타입이다.

값을 제어하고 애니메이션을 적용한다.

DOTween.Init(false, true, LogBehaviour.Verbose).SetCapacity(200, 50);

 

다음과 같이 DOTween의 초기 설정에서 첫번째 인자 값인  autoKillMode를 false로 하면 DOTween의 동작을 재사용 가능하도록 사용할 수 있다.

 

4-3. Sequence

값을 제어하지 않고 다른 Tweener들을 제어한다.

그룹으로 애니메이션을 적용한다.

public class DotweenTest6 : MonoBehaviour
{
    Sequence sequence;

    void Start()
    {
        sequence = DOTween.Sequence();

        sequence.Append(transform.DOMove(new Vector3(0f, 5f, 0f), 2.0f));
        sequence.Join(transform.DORotate(new Vector3(0f, -18f, 0f), 2.0f));
        sequence.Append(transform.DOMove(new Vector3(0f, 36f, 0f), 2.0f));
        sequence.Insert(4.0f, transform.DOScale(new Vector3(1.5f, 1.5f, 1.5f), 1.0f));
        sequence.Prepend(transform.DOScale(new Vector3(0.5f, 0.5f, 0.5f), 2.0f));

    }
}

 

sequence 메소드

  • Append : 트윈 마지막에 추가
  • insert: 일정 시간에 시작
  • join: 앞에 추가된 트윈과 동시 시작
  • Prepend: 맨 처음에 추가

위의 코드에서의 동작 순서

Prepend => Append/Join => Append => Insert

 

 

5. Coroutine 

일반적인 결과는 하나의 프레임 안에서 결과 값을 보여준다.

코루틴은 Enumerator을 사용해서 여러 프레임에 걸쳐서 실행된다.

 

코루틴의 정의: Update() 메서드와 다르게 메서드 제어권을 유니티에 반환하고, 특정 조건이 되면 다시 진행한다.

public class Coroutine1 : MonoBehaviour
{
    public Image image;
    private float alpha;
    private float fadeTime = 3.0f;


    void Start()
    {
        
    }

    void FadeIn()
    {
        while(alpha < 1.0f)
        {
            alpha += Time.deltaTime / fadeTime;
            image.color = new Color(1, 1, 1, alpha);
        }
    }
    
    void Update()
    {
    	if (alpha < 1.0f)
    	{
        	alpha += Time.deltaTime / fadeTime;
        	image.color = new Color(1, 1, 1, alpha);
            }
	}
}

 

일반 메서드 FadeIn()의 경우 최종 결과 값만 반영되어, 서서히 뚜렷해지는게 아니라 그냥 뚜렷한 이미지를 출력한다.

 

update() => 매프레임 마다 결과값이 업데이트 되면서 서서히 이미지가 노출된다.

하지만, Update()에다만 모든 동작을 적용하면 코드가 너무 비대해진다.`

 

public class Coroutune3 : MonoBehaviour
{
    public Image image;
    private float alpha = 0f;
    private float fadeTime = 3.0f;


    void Start()
    {
        StartCoroutine(FadeIn()); 
    }

    IEnumerator FadeIn()
    {
        while (alpha < 1.0f)
        {
            alpha += Time.deltaTime / fadeTime;
            image.color = new Color(1, 1, 1, alpha);
            yield return null;
            
            if (alpha > 0.5f)
            	yield break;
        }
    }
}

 다음과 같이 코루틴을 적용하여 같은 효과를 보여줄 수 있다.

 

▷ 코루틴 사용 조건

  1. MonoBehaviour 상속 받아야 한다.
  2. IEnumerator를 반환하는 힌다.
  3. yield return (조건)을 사용한다. -> yield return을 만나면, 다음 줄부터 실행되는 프레임이 달라진다.
  4. yield break를 만나면 코루틴이 종료된다.
  5. 코루틴을 실행하는 게임 오브젝트가 비활성화되거나 파괴되면 코루틴 종료된다.
    IEnumerator Count()
    {
    	Debug("1");
        yield return new WaitForSeconds(1, 0f);
        
        Debug("2");
        yield return new WaitForSeconds(1, 0f);
        
        Debug("3");
    }

 

▷ 코루틴 사용방법

  1. StartCoroutine() 코루틴 실행 메서드로 실행한다. ( -> 가급적 IEnumerator 방식으로 실행)
  2. StopCoroutine() 코루틴 종료 메서드로 종료할 수 있다. ( -> 가급적 Coroutine 타입으로 사용) 
Coroutine co = StartCoroutine(FadeIn());
Coroutine co2 = StartCoroutine("FadeIn");

StopCoroutine(co);
StopCoroutine(FadeIn());

stopAllCoroutines();

 

  코루틴 작동 방식

C# Enumerator를 활용해서 만들어진다.

=> 차이점은 열거할 때마다 특정 프레임에서 실행되도록 한다.

 

YieldInstruction

=> 코루틴은 일정 시간 대기하고 실행하는 방식으로 작동한다. 해당 방식을 YieldInstruction 클래스가 정의하고 있다.

IEnumerator CoroutineTest()
{
	// 다음 update() 호출시까지 대기 후 실행 
    yield return null;

	// 다음 FixedUpdate() 호출시까지 대기 후 실행
    yield return new WaitForFixedUpdate();

	// float time만큼 시간이 지난 후 첫프레임까지 대기 후 실행
    yield return new WaitForSeconds(time);

	// float time만큼 시간이 지난 후 첫프레임까지 대기 후 실행
    // timeScale의 영향을 받지 않음 (임의로 조정된 시간 반영x)
    yield return new WaitForSecondsRealtime(time);

	// 모든 렌더링 작업이 완료되는 프레임이 끝날 때까지 대기 후 실행
    yield return new WaitForEndOfFrame();

	// 해당 조건이 참이 될 때까지 대기 후 실행
    yield return new WaitUntil(() => Time > 10);

}

 

* StartCoroutine() 하면 garbage가 생기기 때문에 상황에 따라 적절하게 필요한 곳에 사용해야 한다.

내용이 반복적이고 꾸준하게 실행되는 경우에는 Update()를 사용하는 것도 좋다. (update()는 garbage가 발생하지 않는다.)

WaitForFixedUpdate wf = new WaitForFixedUpdate();
    
    // 다음 FixedUpdate() 호출시까지 대기 후 실행
    yield return new WaitForFixedUpdate();
    
    yeid return wf;

=> 하나의 인스턴스로 돌려쓰는게 가능한 캐싱의 경우는 메모리를 아낄 수 있다.

public static readonly WaitForFixedUpdate m_WaitForFixedUpdate = new WaitForFixedUpdate();
public static readonly WaitForEndOfFrame m_WaitForEndOfFrame = new WaitForEndOfFrame();

private static readonly Dictionary<float, WaitForSeconds> m_WaitForSecondsdict = new Dictionary<float, WaitForSeconds>();

public static WaitForSeconds WaitForSeconds(float waitTime)
{
    WaitForSeconds wfs;

    if (m_WaitForSecondsdict.TryGetValue(waitTime, out wfs))
        return wfs;
    else
    {
        wfs = new WaitForSeconds(waitTime);
        m_WaitForSecondsdict.Add(waitTime, wfs);
        return wfs;
    }
}

=> 다음과 같이 딕셔너리를 사용해서 garbage 없이 매개변수를 원하는 초로 입력 받아 사용하는 것도 가능하다.

 


해당 포스트는 게이머TV, [4. 유니티가 어려운 초보자를 위한 유니티 기초 강의], (2024, 9월 22일), 섹션 1. 유니티 기초 강의를 참고하여 작성하였습니다.

 

 

 

Corner Unity

Editor : 니나노

 

728x90

관련글 더보기