해당 애니메이터를 이용하여 설명

Q.StringToHash 메서드는 뭐야?

A.

Animator Class에 있는 메서드애니메이션의 파라미터의 이름(문자열)을 해시값으로 변경하여

최적화 및 성능 개선에 도움을 주는 메서드이다.

 

Q.Animator의 함수라는건 알겠어, 근데 왜 사용하는거야?

A.위에서 말했듯이 최적화 및 성능개선이라고 했다.

애니메이션의 파라미터를 코드로 사용할때

SetBool("isWalking",true);

이런식으로 문자열을 그대로 넣어서 사용하게 되면 파라미터의 문자열을 서로 비교하는 작업을 하는데

이때 처리비용이 크기 때문에 비효율적이라고 하는것이다.

 

하지만 StringToHash를 사용하여 문자열을 Hash값으로 변경해주면

int(정수)는 한 자리만 체크하므로 최적화에 매우 효율적이기 때문이다.

 

또한 문자열 -> 정수( 21억 범위)로 변홤하여 충돌(중복)이 일어날 확률이 매우 낮다!!!
문자열 -> 정수(Hash값)은 항상 동일한 해시값을 반환하기에 다른 파라미터를 건들일 일도 없다.

 

Q.StringToHash 단점이 있을까?

A.

정수 -> 문자열 반환에는 사용하지 못하는 단점이 있다, StringToHash는 일방향으로 작동하는 메서드이기 때문에

 

 p.s

정수(Hash값) -> 문자열은 항상 동일한 문자열을 반환하지 않기에 충돌이 발생 할 수 있다.

 

 

Q.StringToHash는 어떡해 사용해?

public class TopDownAnimationController : AnimationController
{
	//해당 객체에서 사용할 Class 위에 문자열을 해시값으로 변환한후 정적변수로 선언해놓기
    private static readonly int isWalking = Animator.StringToHash("isWalking");
    private static readonly int isHit = Animator.StringToHash("isHit");
    private static readonly int attack = Animator.StringToHash("attack");
 
 	
    //필요할때 해당 변수값을 가져와서 사용하기
 	private void Move(Vector2 vector)
	{
         animator.SetBool(isWalking);
	}

}

이런식으로 미리 문자열을 해시값으로 변환하여 선언한 뒤 (정적으로 생성하여 컴파일하면서 동시에 생성)

필요할때 해당 해시값 변수를 인자값으로 사용하면 된다.

Q.오브젝트 풀링이란?

A. Scene에서 사용할 오브젝트(객체)들을 미리 만들어놓고서 컨테이너에 저장해놓는다.
필요할때 오브젝트 풀링에서 해당 오브젝트를 호출해서 사용한 뒤 다시 컨테이너(오브젝트 풀)에 돌려주는 설계 패턴이다.

 

Q.오브젝트 풀링을 왜 사용하는가?

A.

객체를 생성/파괴하는 메서드인 Instantiate / Destroy는 호출시에 처리비용이 매우 크다.

즉, 처리비용이 매우 크다는 말은 최적화에 안좋다는 뜻이다.

 

미리 객체를 만들어 놓으면 첫 시작할때만 생성을 하기에 실제 인게임시에는 오브젝트를 사용하려고 꺼낼때 처리비용이 크지 않다.

또한 파괴하지 않고 컨테이너(오브젝트 푸)에 돌려주기 때문에 계속 재활용이 가능한 장점도 있다.

 

Q.오브젝트 풀링을 어떡해 사용해?

A.오브젝트 풀링은 프로그래머의 재량에 따라 형태나 많이 바뀐다. 

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{
	//Pool 구조체는 오브젝트 하나를 의미함
    [System.Serializable]
    public class Pool
    {
        public string tag; //오브젝트의 태그
        public GameObject prefab; //오브젝트의 프리팹
        public int size; //Pool에 담을 오브젝트의 갯수
        //TODO CODE : 오브젝트에서 사용할 필드값
    }

    public List<Pool> pools = new List<Pool>(); //사용할 오브젝트를 보관하는 컨테이너
    public Dictionary<string, Queue<GameObject>> PoolDictionary;//실제로 호출하여 사용하게 될 컨테이너

    private void Awake()
    {
        PoolDictionary = new Dictionary<string, Queue<GameObject>>();
		
        //List에 있는 오브젝트를 하나씩 호출하여 n개만큼 생성 후 오브젝트 풀(PoolDictionary)에 담음
        foreach (var pool in pools)
        {
        	//Unity 하이어라키에 오브젝트를 담을 부모 오브젝트 생성
            GameObject poolContainer = new GameObject("Pool_Container_" + pool.tag);
			
            //Queue 자료구조를 사용하여 오브젝트를 관리
            //Queue를 사용한 이유 -> 순서에 상관없는 오브젝트들 사용하기에
            Queue<GameObject> queue = new Queue<GameObject>()
            
            //생서할 오브젝트 갯수만큼 생성
            for (int i = 0; i < pool.size; i++)
            {
            	//오브젝트를 생성 -> 오브젝트를 비활성화 -> 오브젝트 풀(queue)에 담음
                GameObject obj = Instantiate(pool.prefab, poolContainer.transform);
                obj.SetActive(false);
                queue.Enqueue(obj);
            }
			
            //queue에 담은 풀을 Dictionary에 추가 -> 하나의 오브젝트 풀이 완성 
            PoolDictionary.Add(pool.tag, queue);
        }
    }
	
    //외부에서 오브젝트가 필요할때 호출되는 함수
    public GameObject SpawnFromPool(string tag)
    {
    	//오브젝트 풀의 key값을 비교하여 일치하지 않으면 탈출
        if(!PoolDictionary.ContainsKey(tag))
        {
            return null;
        }
		
        //해당 key의 오브젝트를 하나 dequeue
        GameObject obj = PoolDictionary[tag].Dequeue();
        
        //Queue에서 오브젝트 하나가 dequeue됫기에 다시 채워준다.
        PoolDictionary[tag].Enqueue(obj);
		
       	//꺼낸 오브젝트를 활성화
        obj.SetActive(true);
		
        return obj;
    }
}

해당 코드는 총알이나 투사체 및 파티클 같이 순서는 상관없고 재활용을 계속 해야되는 오브젝트에 사용되는 패턴이다.


 

이 코드의 단점은 오브젝트가 하나씩 발사 될때마다 다시 Enqueue로 챙겨주고 있으며 반납을 하지 않고있다.

즉, 투사체들은 오브젝트에 닿을때 해당 조건이되면 Destroy로 파괴되고 있기에 최적화에 좋지 않은 모습을 보이고있다.

 

대처법으로는 외부에서 사용이 완료된 오브젝트를 다시 반납하는 메서드를 만들어 사용하는것이 최적화에 좋다.

 


또 다른 단점은 오브젝트가 많은 숫자를 필요로 만들때 기존 오브젝트 갯수보다 많으면 문제가 생긴다.

예시로 이런 상황이 있다. 

1. 1초에 50개 발사 

2. 오브젝트를 20개만 만들어놓음 
3. 20개를 발사하고 21개쨰를 발사할떄 미리 발사해놧던 20개 중 하나가 사라지고 21개째가 되어 재활용이 되어버리는 문제
4. 총이 나가다가 뒤로 돌아오는 현상이 발생 

이런 부자연스러운 현상이 발생하기에 

 

대처법으로는 Queue대신 List를 활용하여 List 각 하나의 원소에 접근하여 사용중인지를 체크, 만약 모든 오브젝트가 사용 중인경우 추가로 오브젝트 풀에 오브젝트를 추가 생성하는 방식이 있다. 

 


위에 코드는 예시일 뿐이며 , 다른 사람들이 사용한 오브젝트풀링의 좋은 예제가 많이 있다.

여기서는 이런 개념이 있다라는 것을 파악하고 다음 프로젝트 진행시에 사용하는것을 목표로 하자.

[게임 도중 캐릭터 선택 기능]을 구현 중에 데이터를 교체하는게 아닌 Instnate와 Destroy를 이용하여 구현하였다.

나중에 시간이 되면 데이터를 채우는 형식으로 변경하는게 최적화에 더 좋을것 같지만, 우선 과제 제출이 먼저이기떄문에 최적화에 좋지 않지만 이것을 선택했다 하지만. 

 

해당 컴포넌트를 가진 오브젝트를 Destroy 를 하자 문제가 발생하였다.

파괴한 객체에 animator와 InputSystem이 자꾸 접근하려는 문제였다.

 

나는 분명 GameManager에 InputSystem을 컨트롤 하구 있고 Animator는 제대로 사라졌는데 왜 자꾸 사라진 객체에 접근하려는지 이유를 몰랐다.

 

원인은 내가 객체에 AnimationController를 컴포넌트로 추가해놨기 때문이였다.

해당 코드에서 Event함수에 메서드룰 추가 해놨으니 객체가 사라져도

InputSystem은 안사라지고 살아있으나 객체가 사라진 event함수는 갈곳을 잃었기에 문제가 발생하고 있었던것 이였다.

 

 

-대처-

해당 오브젝트가 파괴될떄 갖고 있는 컴포넌트를 전부 null 처리 및 Event함수에 저장된 함수를 제거함으로 트러블슈팅을 해결하였다.

오늘 개발중에 InputActionAsset이 호출이 안되서 알아보니 ScriptableObejct 였다.

 

https://01149.tistory.com/97

 

Unity :: 스크립트에서 InputSystem을 사용하기 위해 InputActionAsset을 호출시 주의할 점

//코드 일부를 가져왔습니다.private InputActionAsset inputActions;inputActions = GetComponent(); 해당 코드는 InputSystem 패키지를 이용하기 위해 스크립트 에서 InputActionAsset을 호출하는 코드이지만계속 Null이 들

01149.tistory.com

 

//코드 일부를 가져왔습니다.

private InputActionAsset inputActions;

inputActions = GetComponent<InputActionAsset>();

 

해당 코드는 InputSystem 패키지를 이용하기 위해 스크립트 에서 InputActionAsset을 호출하는 코드이지만

계속 Null이 들어와 코드가 동작하지 않았다.

 

그래서 구글링 해보고 찾았는데 원인을 찾았다.

 

원인은 InputActionAsset이 컴포넌트가 아닌 ScriptableObject로 관리되기 때문이다.

ScriptableObject는 게임오브젝트에 컴포넌트로 추가될수 없다는 특성을 가지고있다.

 

그러기 때문에 PlayerInput 컴포넌트에 등록되있는  InputActionAsset을 호출해서 쓸 수 없던것이다.

 

그럼 어떡해 호출해야되는것인가?

1. Inspector에서 직접 등록해서 쓰는 방법

2. InputActionAsset 이 아닌 PlayerInput 컴포넌트를 호출하는 방법

private PlayerInput playerInput;
private InputActionAsset inputActions;

void Awake()
{
    playerInput = GetComponent<PlayerInput>();
    inputActions = playerInput.actions; 
}

이렇게 playerInput을 컴포넌트로 호출해서 사용하면된다.

https://www.acmicpc.net/problem/2609

 

문제 해결 일자 : 2024.10.08(화)

난이도 : 브론즈 1(Solved.ac)

문제 풀이 /  코드 구현 :  알고리즘 참고를 위해 검색(구글링) / 직접

더보기

카테고리 : 수학 , 정수론 , 유클리드 호제법


문제 내용 : 입력으로 두 정수가 들어올때, 두 수의 최대 공약수와 최소 공배수를 출력하면된다.

 

문제 풀이 : 

최대 공약수를 구하기 위해서 소인수분해를 하게 되면 시간복잡도에 걸리게 된다

 

유클리드 호제법 이라는것을 사용하면 시간복잡도만 잡을뿐만 아니라 최소공배수도 같이 계산이된다.

나무위키 출처

이렇게 문장으로 보게되면 무슨말인가 부터 의문이 들지만, 이해하면 정말 간단한 말이다.

진짜 중요한 문장이 문장이다.

우리는 반복문으로 나머지가 0일때까지 루프를 돌리다

r=0이 되는 구간이 나오면 b를 최대공약수로 선택하면된다.

 

유클리드 호제법 활용 예시)

 

최소공배수는 (a*b) / gcb(a,b)를 하면 나온다.

 

접근 순서 :

1) 큰수가 작은수를 나눌수 있게 두 정수(a,b)의 대소비교를 한다.

2) 두 정수(a,b)의 /(몫) , %(나머지) 연산 계산을 한다.

3-1) 나머지가 0이 아닌 경우 a,b를 초기화 한다.

a = 두 숫자중 작은숫자(b)

b = 두숫자의 나머지(r)

3-2) 나머자기 0인경우 해당 b를 최대공약수로 선언한뒤 반복문을 탈출한다.

4) 1)과정으로 돌아가 반복한다. 반복문을 탈출한 경우이면 최소공배수 계산을 한다.

 

구현코드 :

더보기
#include <iostream>
#include <string>

using namespace std;


void Swap(int& num1, int& num2)
{
	int tmp = num1;
	num1 = num2;
	num2 = tmp;
}

int main(void)
{
	int num1, num2;

	cin >> num1 >> num2;

    int oldnum1 = num1;
    int oldnum2 = num2;
       
    int result = 0;
    int remainder = 1; 
    int gcb = 0;
    while(remainder != 0)
        {
            if(num1 <num2)
            {
                Swap(num1,num2);
            }
            result = num1 / num2;
            remainder = num1 % num2;

            if(remainder == 0)
            {
                gcb = num2;
                break;
            }
            
            num1 = num2;
            num2 = remainder;
        }
    
    cout << gcb <<endl;

        cout << (oldnum1 * oldnum2) / num2;
	return 0;
}

ScriptableObject는 Unity에서 데이터를 저장하고 관리하는 유연한 데이터 컨테이너이다.

 

ScriptableObject의 특징을 나열하면

1.MonoBehaviour 보다 경령화 되었고 GameObject에 컴포넌트로 추가 할 수 없다.

2.유니티 CallBack 중에서 OnEnable(),OnDisable(),OnDestroy()만 호출이 가능하다.

3.인스턴스화를 하지 않기 떄문에 사본이 여러개가 생성되지 않아 메모리 할당 최적화에 용이하다. 

 

json,xml,csv 등으로 관리하던 데이터를 Unity에서 쉽게 사용 할 수 있도록 편의적인 기능으로 제공한 것이 ScriptableObject 이다.

 

그럼 ScriptableObject는 어떡해 사용하는가?

1. ScriptableObject를 어떡해 설정할건지 선언이 필요하다.

파일 폴더링(폴더 분류 및 정리)는 필수이다.

Class 기반이기에 똑같이 .cs 로 작성을 해주면된다.

 

1.CreataAssetMenu 라는 Attribute 를 통해 포멧에 맞춰 작성해주어야 프로젝트 창에서 문제없이 호출하여 생성이 가능해진다.

2.ScriptableObject로 사용할 Class는 ScriptableObject를 상속해줘야 된다.

3.Header라는 Attribute를 통해 Inspector창에 표시될때 항목을 나눠줄 수 있다.

4. ScriptableObject Class 기반이기에 상속이 가능하다 ( RangedAttackSO는 AttackSO를 상속하고있다)

 

2. ScriptableObject를 .cs로 설정을 다했는데 어떡해 생성해?

제대로 .cs를 작성하였다면 , 프로젝트 창에서 생성시에 위에 이미지처럼 제대로 표시될것이다.

RangedAttackSO를 생성하면 Inspector에서 부모 데이터까지 다 Inspector에서 표시되며 쉽게 데이터 값이 수정 할수있다. 

 

3. ScriptableObject를 생성했으면 어떤식으로 코드에서 사용돼?

CharaterStat 클래스에 attackSO를 멤버변수로 가지게 한상태

캐릭터 스텟에 AttackSO 라는 ScriptableObject를 추가하여 사용하며 필요할때 데이터를 가져와서 호출할 수있다.

 

위의 CharaterStat을 SerializeField화 해놓고 Inspector에서 수정 할수 있게 선언
Charater Stats Handler 컴포넌트에 ScriptableObject(PlayerDefaultAttackSO)를 설정하고 스텟값을 설정하는 모습

 

이런식으로 해당 객체에게 능력치 데이터를 선언할 수 있게 된다.

자식 Class로 형변환하여 사용도 가능

투사체를 발사하는 메서드인데 공격에 관련된 데이터를 ScriptableObject에 담아놔서 이렇게 꺼내 쓸수도 있다.

 

4. 스텟 능력치 말고도 쓸 수 있어?

물론이다.

1.다양한 패턴을 분리하여 ScriptableObject로 만들수도있고

2.FSM 구조같은 형태를 만들어 해당 캐릭 모드를 ScritptableObject로 만들수도 있고

3.미연시 게임의 대화 로직 같은 것도 .csv가아닌 ScritptableObject로도 만들 수있다.

 

위에 예시 든것 뿐만아니라 생각해서 활용하면 어떡해든 만들어 사용 할 수 있다. 

오늘은 두 벡터사이의 거리를 측정 할수있는 Atan2(y,x)메서드 Unity에서 다루는 회전에 대해 TIL을 작성했습니다.

 

https://01149.tistory.com/90

 

게임수학 :: 아크탄젠트( Atan2(y,x) 함수에 대해 ) , 두 객체 사이의 각도 알기

삼각함수에 아크(arc)는 삼각함수의 역함수를 나타내는 것이다.삼각함수는 각도 -> 변의 비율 을 알수 있다면역삼각함수는  변의 비율 -> 각도 를 알수 있다. 그럼 역삼각함수중 왜 아크탄젠트를

01149.tistory.com

https://01149.tistory.com/91

 

Unity :: 오일러각 vs 쿼터니언 , 쿼터니언과 벡터 곱셈

게임 개발을 하면 오브젝트를 회전시켜야 될 일이 정말 많다.회전을 시키면 두가지 개념을 보게 되는데오일러 각과 쿼터니언이다. 오일러 각(Euler Angle)-3차원 공간의 절대적 좌표를 기준으로 물

01149.tistory.com

 

+ Recent posts