부트캠프

[멋쟁이사자처럼 유니티 TIL] 대리자(delegate)와 이벤트 실습, 공격 모션 구현 (+ (Ctrl + H), 리타겟팅)

맛난과자 2024. 6. 12. 23:12

Delegate(대리자)

 

: 메서드의 참조를 저장하고 필요에 따라 호출할 수 있는 형식.

네임 스페이스나 클래스 내에 선언한다.

 

조금 더 자세한 개념은 저번 글을 보면 알 수 있다.

https://snack-game.tistory.com/13

 

[멋쟁이사자처럼 유니티 TIL] UI 슬라이더, 체력바 구현 (+ 델리게이트(delegate), 이벤트)

슬라이더 ● 생성 방법Hierarchy 에서 마우스 오른쪽 클릭  → UI → Slider 를 클릭  ● 구성 요소 슬라이더를 생성하면 자식 개체 여러 개가 함께 생성된다. - Background : 슬라이더를 비활성화 시

snack-game.tistory.com

 

 

델리게이트 이해에는 실습이 직빵이다.

라고 강사님이 말하셨다.

 

 

● 델리게이트를 쓰지 않은 경우

 

    public class Calculator
    {
        public string functionName = string.Empty;    // null 방지
        
        public void Add(int a, int b)
        {
            int result = a + b;
        }

        public void Substract(int a, int b)
        {
            int result = a - b;
        }
        
        public void CalculatorPrint()
        {
            if (functionName == "Add")
            {
                Add(3, 5);
            }
            
            else if (functionName == "Substract")
            {
                Substract(3, 5);
            }
        }
    }

    public class NormalFuncEx : MonoBehaviour
    {
        // Calculator 클래스 참조
        private Calculator calculator;
        private int sumResult = 0;

        private void Start()
        {
            calculator = new Calculator();
        }

        private void Update()
        {
            if (Input.GetKeyDown((KeyCode.A)))
            {
                calculator.functionName = "Add";
                calculator.CalculatorPrint();
            }
            
            if (Input.GetKeyDown((KeyCode.S)))
            {
                calculator.functionName = "Substract";
                calculator.CalculatorPrint();
            }
        }
    }

 

 

 

● 델리게이트를 사용하는 경우

 

// 델리게이트 제작
public delegate void CalcCompletedEventHandler(int result);

public class Calculator
{
    // 델리게이트 선언
    public CalcCompletedEventHandler CalcCompletedEventHandler;

    public void Add(int a, int b)
    {
        int result = a + b;
        // 대리자에 구독되어 있는 함수 실행
        CalcCompletedEventHandler?.Invoke(result);
    }
    
    public void Substract(int a, int b)
    {
        int result = a - b;
        // 대리자에 구독되어 있는 함수 실행
        CalcCompletedEventHandler?.Invoke(result);
    }
}

public class delegateExample : MonoBehaviour
{
    private Calculator calculator;
    private int sumResult = 0;
    
    void Start()
    {
        calculator = new Calculator();
        
        // delegate에 CalcCompleted 함수를 구독시킴
        calculator.CalcCompletedEventHandler += CalcCompleted;
    }

    void CalcCompleted(int result)
    {
        Debug.Log($"result : {result}");
        sumResult = result;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // Calculator의 Add 함수 호출
            calculator?.Add(sumResult, 1);
        }
		
        if (Input.GetKeyDown((KeyCode.A)))
        {
            // Calculator의 Add 함수 호출
            calculator?.Add(sumResult, 1);
        }
            
        if (Input.GetKeyDown((KeyCode.S)))
        {
            // Calculator의 Subtract 함수 호출
            calculator?.Subtract(sumResult, 1);
        }
        
        // 실행 중단 조건
        if (sumResult > 5000 && calculator is { CalcCompletedEventHandler: not null })
        {
            // Add 함수에서 실행한 것은 대리자이므로 대리자에서 제거하면 함수 실행 불가
            calculator.CalcCompletedEventHandler -= CalcCompleted;
        }
    }
}

 

위와 달리 직접적인 참조도 없어 다른 클래스의 함수를 쉽게 호출할 수 있으며,

코드의 양도 줄어들었다.

 

 

 

delegate는 함수가 많을 때 효과가 나타난다.

 

 

● 여러 함수를 가질 때, 델리게이트를 쓰지 않은 경우

 

// Normal Case
    
    string funcName = "";
    
    // 여러 함수 생성
    void Func1()
    {
        Debug.Log("Func1");
    }
    
    void Func2()
    {
        Debug.Log("Func2");
    }
    
    void Func3()
    {
        Debug.Log("Func3");
    }
    
    void Func4()
    {
        Debug.Log("Func4");
    }
    
    void Func5()
    {
        Debug.Log("Func5");
    }

    // 조건 변수 정의
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            funcName = "Func1";
        }
        
        else if (Input.GetKeyDown(KeyCode.S))
        {
            funcName = "Func2";
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            funcName = "Func3";
        }
        else if (Input.GetKeyDown(KeyCode.W))
        {
            funcName = "Func4";
        }
        else if (Input.GetKeyDown(KeyCode.Q))
        {
            funcName = "Func5";
        }
 
    }

    // 함수 실행
    private void LateUpdate()
    {
        if (funcName == "Func1")
        {
            Func1();
        }
        else if (funcName == "Func2")
        {
            Func2();
        }
        else if (funcName == "Func3")
        {
            Func3();
        }
        else if (funcName == "Func4")
        {
            Func4();
        }
        else if (funcName == "Func5")
        {
            Func5();
        }
    }

 

 

● 여러 함수를 가질 때, 델리게이트를 사용하는 경우

 

// 대리자 선언
public delegate void action();

public class ActionOrFunc : MonoBehaviour
{
    // 대리자 인스턴스 생성
    private action _action;
    
    // 여러 함수 생성
    void Func1()
    {
        Debug.Log("Func1");
    }
    
    void Func2()
    {
        Debug.Log("Func2");
    }
    
    void Func3()
    {
        Debug.Log("Func3");
    }
    
    void Func4()
    {
        Debug.Log("Func4");
    }
    
    void Func5()
    {
        Debug.Log("Func5");
    }

    // 바로 대리자에 함수 참조
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            _action = Func1;
        }

        else if (Input.GetKeyDown(KeyCode.S))
        {
            _action = Func2;
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            _action = Func3;
        }
        else if (Input.GetKeyDown(KeyCode.W))
        {
            _action = Func4;
        }
        else if (Input.GetKeyDown(KeyCode.Q))
        {
            _action = Func5;
        }
    }
    
    // 함수 실행
    private void LateUpdate()
    {
        _action?.Invoke();
    }
}

 

코드의 양이 훨씬 줄고, 직관적이다.

 

 


 

 

Action & Func

 : c# 에서 미리 정의되어 있는 델리게이트

 

둘 다 여러 개의 파라미터를 가질 수 있다.

 

Action은 반환값이 없고, Func은 반환값이 있다.

 

 

 

● Action 사용 예시

 

private Action<int, float, bool> action;

public void RegistAction(Action<int, float, bool> _action)
{
    _action = action;
}

//...(중략)...

private void LateUpdate()
{
    action?.Invoke(1, 2.2f, true);
}

 

 


● Func 사용 예시

 

private Func<int, float, string> func;

public void RegistFunc(Func<int, float, string> _func)
{
    _func = func;
}

private void LateUpdate()
{
    string printResult = func?.Invoke(1, 2.0f);   // 반환값 가짐
    
    if (printResult != null)
        Debug.Log(printResult);
}

 

 


 

Ctrl + H

 

현재 쓰고 있는 라이더에는 Ctrl + H 단축키가 존재하는데,

 

이를 이용하면 넓은 범위에 있는 같은 글자들을 아주 편리하게 고칠 수 있다.

 

() 를 (int a, float b)로 수정하는 과정

 

 

수정을 원하는 해당 범위를 선택 후, 단축키를 입력하면 상단에 입력칸 두 개가 붙어 있는 창이 뜬다.

 

위에는 수정이 필요한 글자, 아래에는 수정할 글자를 입력한다.

 

Replace 버튼, 또는 Replace All 을 누르면, 아래와 같이 수정이 완료된다.

 

수정된 모습

 

 

입력칸의 별 아이콘을 클릭하면 정규식을 이용할 수도 있다고 하셨는데, 아직 정규식을 잘 모르니 패스.

 

많이 써먹자.

 


 

공격 모션 구현

 

 

언제나 그랬든 unitychan 에셋의 안의 모션 중 하나를 사용하려고 했는데....

읎다.

그래서 다른 곳에서 모션을 가져와 리타겟팅을 하게 되었다.

 

 

리타겟팅

 

먼저 원하는 모션을 다운받는다.

본인은 Mixamo에서 다운을 받았다.

https://www.mixamo.com/#/

 

Mixamo

 

www.mixamo.com

 

 

이때, 현재 가지고 있는 모델로 진행할 것이므로, Skin을 Without Skin으로 설정하고 다운받는다.

 

 

다운받은 fbx 파일을 에셋의 애니메이션 폴더에 저장해준다.

파일 안의 애니메이션을 캐릭터의 애니메이터에 드래그한다.

드래그 한 애니메이션은 이제 State가 되었다.

이 State 를 오른쪽 마우스 클릭한 뒤, 'MakeTransition'을 선택하여 나온 선을 Default State에 연결한다.

 

 

캐릭터의 스크립트에 들어가 아래와 같이 코드를 추가한다.

공격 모션 도중에 키를 입력하면 다시 공격 모션이 시작되겠끔 하고 싶었어서

CrossFade 함수를 이용하였다.

if (Input.GetKeyDown(KeyCode.O))
{
        Animator animator = GetComponent<Animator>();        
        animator.CrossFade("Punching", blendTime, -1, 0f);
}

 

 

Animator.CrossFade()

: 애니메이션 State와 다른 State 사이를 부드럽게 전환하기 위해 사용되는 함수

 

- normalizedTransitionDuration: 블렌딩 시간. CrossFade 전환 시간.

 

- layer: 상반신과 하반신을 분리할 때 쓰인다. 기본 상태(baseLayer)는 -1 값을 가진다.

 

- normalizedTimeOffset : 애니메이션 길이를 0~1이라고 봤을 때의 재생 시작 지점.

 

 

 

Trigger를 써도 되는데 AnyState를 써야하고 강사님 왈 나중에 번거롭게 되어 비추천한다고 한다.

 

 

 

결과

 

잼민 펀치 날리는 찬

 

씹히는 키 입력 없이 공격 모션이 잘 구현되었다. 

 

 


 

 

 

델리게이트 이해하는데 머리터지는 줄 알았다. 

아직 어디에 쓸지는 감이 오지 않는다.

포폴 때 의식해서 써보려고 해야 겠다.

 

CrossFade라는 새로운 함수를 알게 되어 좋았다.