최근에 UI 애니메이션을 작업하다 정리가 필요한 부분이 있어서 작성한다.

 

2D Sprite

2D 스프라이트의 애니메이션은 리소스와 Animator를 가지고 만들 수 있다.

Sprite Renderer Animation

 

원하는 프레임마다 스프라이트를 변경하는 방식으로 애니메이션을 만들 수 있다.

 

UI Animation

UI 경우에도 애니메이션으로 컨트롤하는 경우에는 동일한 방식으로 처리된다.

UI Animation

 

Effect

Sprite Renderer를 사용하는 상황에서 뭔가 발산하는 이펙트를 사용한다면 어떻게 하는 게 좋을까 생각하면서 파티클 시스템과 오브젝트를 직접 제어하는 방법 두 가지를 사용해 보았다.

 

 

있는 리소소 가지고 대충 테스트만 하려고 만들다 보니 별로 이뻐 보이진 않는다.

 

이러한 상황에서는 오브젝트를 가지고 직접 제어하는 게 나은 방식인 거 같다.

 

 

실제로 사용한다면 풀링을 해서 쓰겠지만 그럼에도 상당히 많은 오브젝트를 뿜어낸다면 파티클을 쓰는 게 적합하다고 보인다.

 

상황에 맞게 잘 조율하는 게 필요하다.

 

위에서 사용한 코드

 

using UnityEngine;

public class Coin : MonoBehaviour
{
    private const float power = 10f;
    private void OnEnable()
    {
        transform.position = Vector3.zero;
        ApplyForce();
    }

    private void ApplyForce()
    {
        var rb = GetComponent<Rigidbody2D>();
        float randomHorizontal = Random.Range(-1f, 1f); // -1 to 1 for left/right
        Vector2 direction = new Vector2(randomHorizontal, 1f).normalized;
        rb.AddForce(direction * power, ForceMode2D.Impulse);
    }
}

using UnityEngine;

public class Coin : MonoBehaviour
{
    private const float power = 10f;
    private void OnEnable()
    {
        transform.position = Vector3.zero;
        ApplyForce();
    }

    private void ApplyForce()
    {
        var rb = GetComponent<Rigidbody2D>();
        float randomHorizontal = Random.Range(-1f, 1f); // -1 to 1 for left/right
        Vector2 direction = new Vector2(randomHorizontal, 1f).normalized;
        rb.AddForce(direction * power, ForceMode2D.Impulse);
    }
}

 

대충 동전이 뿜어지는 듯한 모습을 중점으로 만들었다.

 

Coin 간에는 충돌처리하면 초기 한 위치에 모여있을 때 서로 부딪혀서 Physics2D > Layer Collision Matrix를 꺼두었다.

 

바닥에 충돌 후 튕기는 것과 미끄러지는 정도는 Phsics Material로 조절한다 (Friction 마찰력, Bounciness 탄성력)

 

UI Effect

위 내용들은 겸사겸사로 같이 정리한 내용이고 이 글을 쓰기 시작한 이유는 이 부분 때문이었다.

 

상황은 대충 이렇다.

 

간단한 퍼즐 게임을 만드는 중에 스프라이트 렌더러를 쓰기에는 귀찮은 부분들이 있어서 간단하게 만들려고 UI를 베이스로 해서 게임 로직들을 만들었고 그렇게 진행하다 보니 효과를 추가하는 과정에서 물리적인 부분을 사용할 수 없어 직접 위치를 이동시켜서 유사하게 재현할 수밖에 없었다.

 

UI Animation

 

using UnityEngine;

public class UICoinMaster : MonoBehaviour
{
    [SerializeField] UICoin[] uiCoins;

    bool isOn = false;
    public void OnClick_UICoinMaster()
    {
        if (isOn)
        {
            foreach( var coin in uiCoins)
            {
                coin.gameObject.SetActive(false);
            }
            isOn = false;
        }
        else
        {
            foreach (var coin in uiCoins)
            {
                coin.gameObject.SetActive(true);
            }
            isOn = true;
        }
    }
}


using UnityEngine;

public class UICoin : MonoBehaviour
{
    [SerializeField] private float initialForce = 10f;
    [SerializeField] private float gravity = 9.8f;
    [SerializeField] private float bounceForce = 0.5f;
    [SerializeField] private bool isMoving = false;

    private RectTransform rect;
    private Vector2 velocity;
    private Vector2 originPos;
    private void Awake()
    {
        rect = GetComponent<RectTransform>();
        originPos = rect.position;
    }

    private void OnEnable()
    {
        ApplyPower();
    }

    private void OnDisable()
    {
        rect.position = originPos;
    }

    private void ApplyPower()
    {
        float randomX = Random.Range(-1f, 1f);
        velocity = new Vector2(randomX, 1f).normalized * initialForce;
        isMoving = true;
    }

    private void Update()
    {
        if (!isMoving) return;

        velocity.y -= gravity * Time.deltaTime;

        rect.anchoredPosition += velocity * Time.deltaTime;

        if (rect.anchoredPosition.y < 0)
        {
            rect.anchoredPosition = new Vector2(rect.anchoredPosition.x, 0);
            velocity.y = -velocity.y * bounceForce;
            velocity.x *= 0.8f;
            
            if (Mathf.Abs(velocity.y) < 0.1f)
            {
                isMoving = false;
            }
        }
    }
}

 

UI 크기에 맞춰서 값들을 적절하게 세팅하면 그럴듯해 보인다. 구현하는 데는 크게 무리가 없지만 UI 가지고 이래도 되나 싶은 생각이 들기도 한다.

 

개인적으로는 필요한 기능만 직접 구현해서 쓰는 걸 선호하는 편이라 잘 쓰진 않지만 위처럼 UI를 제어하는 데는 DoTween을 사용하는 게 더 간단하고 다양한 동작들도 처리할 수 있을 것이다.

 

UI Particle System

한 번쯤은 'UI위에 파티클 뿌리기'에 대해서 많은 고민과 탐구를 해보았을 것이다. 이 경우 나는 주로 카메라의 렌더링 모드를 Screen Space - Camera로 세팅해서 파티클을 보이게 하는 방법을 사용했다. 그런데 이 방법은 원하는 파티클을 사용할 때나 좌표계를 다룰 때 은근히 귀찮고 까로운면이 있었다.

 

그래서 이번에 좀 더 찾다 보니 좋은 방법을 알게 되었다.

 

https://github.com/Unity-UI-Extensions/com.unity.uiextensions.git

 

GitHub - Unity-UI-Extensions/com.unity.uiextensions

Contribute to Unity-UI-Extensions/com.unity.uiextensions development by creating an account on GitHub.

github.com

 

 

UI Extensions 라이브러리로 여기에서 UIParticle System을 사용하면 파티클을 UI 위에 렌더링 할 수 있는 상태로 만들 수 있다.

 

해당 라이브러리에 대해서는 알고는 있었는데 파티클 관련 기능이 있었다는 건 이번에 알게 되었다.

 

이와 관련해서 UIParticle System 사용방법과 파티클의 기본 사용방법을 배우기 좋은 영상을 메모해 둔다.

 

https://www.youtube.com/watch?v=hiRdux33UCs

 

파티클을 조금 참고해서 만들어 적용시켜 본다.

 

효과를 주고 싶은 UI의 자식에 파티클을 할당하면 UI와 동일한 렌더링 우선순위로 처리가 된다는 점에서 원하는 기능 그 자체였다.

 

 

Performance

겸사겸사로 추가된 내용들이 많지만 결국 UI로 동작들을 구현해 버리면 성능상에 좋지 않은 건 사실이고 권장되는 방식도 아니다.

가장 큰 이유는 UI는 변경될 때마다 캔버스가 리빌드를 돌리기 때문에 이러한 동작이 과도하게 발생되며 이 연산은 CPU에서 처리하기 때문에 신경이 안 쓰일 수 없지만 어느 정도 타협해서 사용한다면 괜찮지 않을까 생각도 든다.

 

UI로 만들어봤자 엄청나게 복잡한 게임도 아닐 것이고 퍼즐 게임 정도면 성능상에 이슈를 발생시킬 만큼은 아닐 것이라고 감히 예상한다.

 

정말 괜찮은지는 프로파일링을 해봐야겠지만 궁금하기도 하니 나중에 시간 날 때 게임 오브젝트와 UI로 구현한 것을 각각 최대한 비슷하게 만들어 놓고 성능을 한 번 비교해 보는 것이 괜찮을 것 같다.

 

728x90
반응형

Player 세팅에서 Input System Package만 사용하는 상태

 

게임 뷰 창에서 버튼이 클릭되지 않는 문제가 발생했다.

 

하지만 시뮬레이터 창, 빌드 후에는 정상적으로 동작이 되는 문제가 있었다.

 

이런저런 방법을 시도해 보다가 혹시나 해서 시뮬레이터 창을 닫고 다시 플레이해 보니

 

정상적으로 동작이 되었다.

 

두 창이 모두 활성화된 상태에서 플레이될 때 에디터루프에서 인풋시스템이 초기화하면서 문제가 생긴 건지 별거 아닌 문제 때문에 꽤 시간을 빼앗긴 일이 생겨서 메모해 둔다.

728x90
반응형

Arrow Function

화살표 함수

화살표 함수는 return 밖에 없는 함수를 줄여서 표현할 수 있는 함수로, 람다식이라고도 부른다.

 

function add(a, b){
	return a + b;
}
console.log(add(1, 4)); // 5

// Arrow Function
const add = (a, b) => {
	return a + b;
}

// 또는 
// const add = (a, b) => a + b;
console.log(add(1, 4)); // 5

 

 

람다식의 표현은 함수의 앞에 매개변수를 괄호로 묶고 화살표 연산자를 사용한 뒤에 함수의 본문을 작성한다.

 

화살표 함수의 특징은 함수명, arguments, this 세 가지가 없다.

 

함수명이 없으므로 익명 함수로 동작하게 된다.

 

Anonymous Function

익명 함수

익명 함수는 이름이 없는 함수로 함수 코드가 변수명에 저장된 형태이다.

 

따라서 변숫값으로 구성된 함수 코드를 다른 변수명에 변수를 대입하듯이 사용되거나 다른 함수의 인수로 전달하는 방식으로 주로 사용된다.

 

const func = function() {
	console.log("Anonymous Function");
};

 

add라는 이름의 변수에 화살표 함수로 만든 이름 없는 익명 함수가 할당된다.

 

이렇게 익명함수는 간단한 표현으로 가독성과 간결함이 좋기 때문에 주로 일회성 작업이나 콜백 함수로 사용된다.

 

One-time Use

일회성 사용

익명 함수는 함수가 특정 작업을 수행한 후 다시 호출될 필요가 없는 경우와 같이 일회성으로 사용되는 경우가 많다.

 

setTimeOut(function() {
	console.log("Time Out");
}, 2000);

 

setTimeOut에 전달된 익명 함수는 2초의 시간 지연 후 한 번만 실행되게 된다.

 

변수나 프로퍼티에 할당 가능

일반적으로 변수나 객체의 프로퍼티에 할당될 수 있다. 이를 사용해서 여러 곳에서 재사용이 가능하다.

 

conse obj = {
	greet : function() {
    	console.log("Hello");
    }
};
obj.greet(); // Hello

 

greet 프로퍼티에 할당된 익명 함수는 객체를 통해서 여러 번 다시 사용이 가능하다.

 

자신을 참조할 수 없음

익명 함수는 이름이 없기 때문에 일반적으로 함수 내부에서 자신을 직접 참조할 수 없다.

 

따라서 재귀 호출과 같이 자신을 호출이 필요한 경우에는 이름이 있는 함수를 사용하는 것이 좋지만 이를 우회하여 표현식에 이름을 명시적으로 붙일 수 있다.

 

const factorial = function f(n) { // 이름 f 명시
	if (n <= 1) return 1;
    return n * f(n - 1);
};
console.log(factorial(5)); // 120

 

f는 함수 표현식의 이름이지만 factorial 변수에 익명 함수처럼 할당되어 내부에서 f를 사용하여 재귀 호출이 가능하다.

 

this 바인딩 없음

익명 함수 중에서도 화살표 함수는 자체적인 this를 가지지 않고 선언된 환경의 this를 상속받는다.

 

하지만 일반적인 익명 함수는 호출 문맥에 따라 this를 설정하는데 this 바인딩이 없기 때문에 화살표 함수의 경우 다르게 동작한다.

 

// anonymous func
const obj = {
  name: "bak",
  regularFunction: function() {
    console.log(this.name);
  }
};
obj.regularFunction(); // bak 

// arrow func
window.name = "global scope";
const obj = {
  name: "bak",
  arrowFunction: () => {
    console.log(this.name);
  }
};
obj.arrowFunction(); // global scope

 

일반적인 익명 함수에서 this는 문맥, 즉 함수가 호출되는 시점과 방식에 따라서 그 객체가 this가 된다.

 

하지만 화살표 함수는 고유한 this 바인딩이 없고 대신에 함수를 정의한 상위 스코프의 this를 상속받게 된다.

 

obj의 메서드로 정의된 것처럼 보이는 화살표 함수는 이러한 바인딩이 없는 특성으로 인해서 상위 스코프의 this를 상속받게 되어 위의 예제의 경우처럼 화살표 함수는 자신이 정의된 시점의 this를 상속받게 된다

 

따라서 this의 객체는 obj가 아닌  전역 객체 this를 가리키게 되고 this.name 은 window.name을 출력하게 된다.

728x90
반응형

+ Recent posts