Q.SkyBox가 뭐야?

A.월드스페이스의 배경을 둘러싸는 환경 매핑 기술 이다.

 

Unity에서는 Scene의 BackGround로 주로 사용되며 보통 자연적인 환경(하늘,구름,산)을 표현할때 많이 사용

미리 구성된 스카이박스를 가져와서 사용 할수 있어 Asset으로도 활용도가 높음

 

이 SkyBox를 동적으로 변경하여 낮과 밤 등의 시간대나 특정 이벤트 연출을 줄 수있지만

단점으로는 아무래도 텍스쳐로 맵핑하는 그래픽렌더링이 들어가니 최적화는 좋지 않다.

 

Q.SkyBox를 그럼 어떡해 써??

A.SkyBox는 Material로 구성되어있으면 Material의 Shader를 변경하여 사용하면된다.

 

Skybox는 Material로 구성되어있으며 Shader의 Type을 변경하여 사용하면된다.
Skybox-Procedural Type으로 구성한 배치

 

어떤 Type을 선택하든 공통적으로 Tint Color(Procedural은 Sky와 Ground 2개로 설정가능) , Exposure를 제어 할 수 있다.

기능 이름 설명
Tint Color Skybox의 색상을 결정 합니다. 
Exposure 스카이박스의 밝기를 조정합니다.

 

Skybox를 선택하면 4가지 타입에 대한 설명입니다. 

Skybox의 Type 설명
6-Sided 여섯개의 텍스처를 사용하여 위,아래,왼쪽,오른쪽,앞,뒤에 배치하여 사용됩니다.

하지만 이 Type의 단점은 6면의 리소스(이미지)를 전부 구해야하니 관리 측면에서는 힘들수가 있다.
CubeMap 여섯개의 텍스처를 사용하여 위,아래,왼쪽,오른쪽,앞,뒤에 배치하여 사용됩니다.

그대신 하나의 리소스(이미지)로 6면을 전부 맵핑 할 수 있습니다.
Panoramic(파노라마) 파노라마가 구체형 스카이박스리소스(이미지)를 구체형으로 맵핑하여 사용 할 수있습니다.

360도(수평) ,180도(수직) 이 두종류중 하나를 골라 사용합니다.
Procedural(절차적) 미리 정의된 텍스처가 아닌 유니티의 절차적 알고리즘을 통해 하늘을 동적으로 생성하는 방식으로 시간의 흐름을 나타내기 좋습니다.

하늘의 색상,태양 및 달의 위치, 대기현상 , 낮과 밤 등 다양한 시간적 흐름 연출을 나타낼 수 있습니다.

 

Q.Skybox Material도 만들고 Shader도 선택해서 inspector에 속성값도 다  조절했어 어떡해 적용해야돼?

A. Window - Rendering - Lighting → Lighting(Environment) → Material을 변경하면 적용 됩니다.

Lighting - Environment 창에서 Material을 변경해주면 된다.

 

Skybox가 적용된 월드

게임 개발을 공부하면 오브젝트 풀에 대해 배우면서

오브젝트를 미리 생성하고 필요할때 꺼내서 사용하고 사용이 다되면 풀에 반납하여 비활성화 시켜주는 것을

오브젝트 풀링이라고 배워왔다.

 

하지만 유니티에서는 이 오브젝트 풀을 사용 할수 있게 패키지로 제공하고 있다.

 

using UnityEngine.Pool;

해당 패키지를 사용하기 위해서는 해당 코드를 작성하고 사용해야된다.

 

해당 Pool를 사용하려면 어떡해 해야되는가?

public IObjectPool<GameObject> objectPool;
public ObjectPool<GameObject> pool;

변수로 선언할때는 이 두방식을 이용하여 변수를 초기화 하여 사용하면된다.

 

초기화시 필요한 생성자 매개변수들

 pool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject, DestroyObject, collectionChecks, minSize, maxSize);
 objectPool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject, DestroyObject, collectionChecks, minSize, maxSize);

이런식으로 선언을 하면된다. 

 

namespace UnityEngine.Pool
{
    public interface IObjectPool<T> where T : class
    {
        int CountInactive { get; }

        T Get();

        PooledObject<T> Get(out T v);

        void Release(T element);

        void Clear();
    }
}

 

해당 코드가 IObjectPool의 내부 구조이다.

 

using System.Collections.Generic;
using System.ComponentModel;
using UnityEditor.Presets;
using UnityEngine;
using UnityEngine.Pool;


//최소 50개의 오브젝트 수 보장, 부족할 경우 누적 300개까지 추가 생성, 300개가 넘어갈 경우 가장 오래전에 생성된 오브젝트를 반환 후 재사용
public class Week2_OjbectPool_Q4 : Singleton<Week2_OjbectPool_Q4>
{

    [SerializeField] private GameObject prefab;
    public ObjectPool<GameObject> pool;

    public bool collectionChecks = true;
    private const int minSize = 50;
    private const int maxSize = 300;

    private GameObject container;
    void Awake()
    {
        container = new GameObject(prefab.name + "_Container");

        /*
        createFunc: 오브젝트 생성 함수 (Func)
        actionOnGet: 풀에서 오브젝트를 가져오는 함수 (Action)
        actionOnRelease: 오브젝트를 비활성화할 때 호출하는 함수 (Action)
        actionOnDestroy: 오브젝트 파괴 함수 (Action)
        collectionCheck: 중복 반환 체크 (bool)
        defaultCapacity: ObjectPool의 List<T>에 미리 자리 만드는것(오브젝트 생성x)(int)
        maxSize: 저장할 오브젝트의 최대 갯수 (int)
         */
        pool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject, DestroyObject, collectionChecks, minSize, maxSize);
    }

    private GameObject CreateObject()
    {
        // [요구스펙 1] Create Object

        //Instantiate();
        GameObject obj = Instantiate(prefab, container.transform);
        return obj;
    }

    public void GetObject(GameObject obj)
    {
        // [요구스펙 2] Get Object
        obj.gameObject.SetActive(true);
    }

    public void ReleaseObject(GameObject obj)
    {
        // [요구스펙 3] Release Object
        obj.gameObject.SetActive(false);
    }

    public void DestroyObject(GameObject obj)
    {
        //오브젝트 파괴 함수
        Destroy(obj.gameObject);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            for (int i = 0; i < 10; i++)
            {
                pool.Get();
            }
        }
    }
}

해당 코드는 UnityEngine.Pool을 이용하여 오브젝트 풀을 구성한 예제 코드이다. 

사용시에는 따로 풀링할 오브젝트를 넣어 등록해서 사용해야된다. 

 

 

해당 Docs를 참고하여 글을 작성하였습니다. 

https://docs.unity3d.com/ScriptReference/Pool.ObjectPool_1.html

 

Unity - Scripting API: ObjectPool<T0>

Object Pooling is a way to optimize your projects and lower the burden that is placed on the CPU when having to rapidly create and destroy new objects. It is a good practice and design pattern to keep in mind to help relieve the processing power of the CPU

docs.unity3d.com

 

오브젝트 풀링 만들려고 구조 잡는중에 
ObjectPool을 제네릭으로 만들고 게임오브젝트 생성하듯이 하려고했으나
자꾸 null값을 반환하여 왜그런지 찾아보니 
제네릭 클래스는 컴포넌트화하지 못한다 , 즉, 오브젝트로 못만든다는 뜻이다.
그래서 Monobehaviour를 해지하고 C#문법을 이용하여 Class를 호출하여 사용하여아한다. 

 

public class ObjectPool<T> : MonoBehaviour where T : Component
{
    public Queue<T> PoolQueue;

    public void Start()
    {
        PoolQueue = new Queue<T>();
    }

    public void Initailize(int poolCount, T poolObject)
    {
        GameObject poolContainer = new GameObject("Pool_Container_" + poolObject.name);

        T gameObj;
        for (int i = 0; i < poolCount; i++)
        {
            gameObj = Instantiate(poolObject, poolContainer.transform);
            gameObj.gameObject.SetActive(false);

            PoolQueue.Enqueue(gameObj);
        }
    }
}

 /*====================================================================*/

public class EnemySpawnManager : SpawnManager<TestMonster>
{
  
  private void Start()
    {
        foreach (TestMonster prefab in prefabesList)
        {
            prefab.OnEventPushObject += PushObject;
			
            //트러블슈팅이 난구간 objectPool을 자꾸 null을 반환한다.
            ObjectPool<TestMonster> objectPool = new ObjectPool<TestMonster>();
            
            objectPool.Initailize(prefab.PoolCount, prefab);
            objectPools.Add(prefab.name, objectPool);
        }

        Initialize();
    }
}

 

해당 오브젝트의 objectPool을 받아오려 했으나 자꾸 null을 반환해서 문제가 발생하였다.

 

//트러블슈팅 대처 : Mononehaviour 를 상속해지하였음 
public class ObjectPool<T> where T : MonoBehaviour
{
    public Queue<T> PoolQueue = new Queue<T>();
    
    public Queue<T> PoolQueue = new Queue<T>();
	
    //Initialize 메서드를 변경하였음 (트러블슈팅과 문제되는부분 x)
    public bool InitPushObject(T poolObject)
    {
        if (poolObject == null) 
        {
            Debug.Log(poolObject.name + "이 null 입니다.");
            return false;
        }

        poolObject.gameObject.SetActive(false);
        PoolQueue.Enqueue(poolObject);

        return true;
    }
}

/*===========================================================*/

public class EnemySpawnManager : SpawnManager<TestMonster>
{
	private void Awake()
	{
    	TestMonster gameObj;

        foreach (TestMonster prefab in prefabesList)
        {
            GameObject poolContainer = new GameObject("Pool_Container_" + prefab.name);
			
            //트러블슈팅 대처 : 
            //Monobehaviour를 상속해지해서 new 어트리뷰트를 이용해여 클래스 객체 생성을 함
            ObjectPool<TestMonster> objectPool = new ObjectPool<TestMonster>();

            for (int i = 0; i < prefab.PoolCount; i++)
            {
                gameObj = Instantiate(prefab, poolContainer.transform);
                gameObj.OnEventPushObject += PushObject;
                objectPool.InitPushObject(gameObj);
            }

            objectPools.Add(prefab.monsterName, objectPool);
        }

    	Initialize();
	}
}

ObjectPool의 Monobehaviour를 상속해지하고 C# 문법의 new로 객체를 생성한뒤 호출하여 사용하였다.

=============================2024.10.21 수정=============================================

 

제네릭을 상속 받은 클래스들 -> EnemySpawnManager는 이미 오브젝트로 만들어 진다.

하지만 ObjectPool<T>를 컴포넌트 및 오브젝트화해서 사용하지 못한다.

즉, 제네릭 클래스 그 자체로는 사용하지 못하고 상속을 해서 사용해야 올바른 사용법이다.

 

Q.Slerp가 뭐야?

A.

Slerp는 구면선형보간(Spherically interpolate)을 의미한다.

구면선형보간은 두 지점 사이의 위치를 파악하는 선형보간(lerp)와 같지만 직선이 아닌 곡선으로 파악을한다.

 

이미지 출처 : https://gnaseel.tistory.com/14

Q.Slerp가 무슨뜻인지 알겠어, 그럼 Unity에서 어떡해 써??

A.

time 내에서 0 ~ 1 로 분할한뒤 위치를 적용시켜준다.

Vector3 start;
Vector3 end;
float time;

transform.position = Vector3.Lerp(start,end,time); // 선형 보간 

transform.position  = Vector3.SLerp(start,end,time); // 선형 보간

 

Lerp 레퍼런스

https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html

 

Unity - Scripting API: Vector3.Lerp

Interpolates between the points a and b by the interpolant t. The parameter t is clamped to the range [0, 1]. This is most commonly used to find a point some fraction of the way along a line between two endpoints (e.g. to move an object gradually between t

docs.unity3d.com

Slerp 레퍼런스

https://docs.unity3d.com/ScriptReference/Vector3.Slerp.html

 

Unity - Scripting API: Vector3.Slerp

Interpolates between a and b by amount t. The difference between this and linear interpolation (aka, "lerp") is that the vectors are treated as directions rather than points in space. The direction of the returned vector is interpolated by the angle and it

docs.unity3d.com

 

Q.Slerp를 이용해서 Unity2D 객체 회전시키기 

A.회전을 바로 하는게 아닌 구면 보간을 이용하여 자연스러운 회전을 만들고싶을때 사용된다.

 

이미지 출처 : https://blog.naver.com/eaya13/222170877065

왼쪽이 3D 월드 좌표축 , 오른쪽이 2D월드 좌표축이다.

즉, 유니티 2D에서 회전을 적용시키고 싶으면 x 축이 아닌 z축을 회전 시켜야되는것이다. 

왼쪽 : x축으로 회전 , 오른쪽 : z축으로 회전

 

    private void Rotate(float inputX)
    {
    	//AngleAxis() : axis를 중심으로 angle(inputX)만큼 회전시킨 쿼터니언을 반환한다.
        Quaternion Rot = Quaternion.AngleAxis((inputX), Vector3.forward);

        transform.rotation = Quaternion.Slerp(transform.rotation, Rot, ROTATIONSPEED);
    }

 

'Unity > Unity2D' 카테고리의 다른 글

Unity2D :: 타일맵을 이용한 오브젝트 꾸미기  (0) 2024.10.07

Q.Unity TMP에 있는 폰트로 한글을 쓰려니 자꾸 깨져서 나와

A.

한글이 적용되는 폰트로 TMP Asset을 만들어주면된다.

 

1.Window - TextMeshPro - Font Assets Creator 로 들어가 설정창을 연다.

 

2. Font Assets Creator의 위에서부터 빨간상자 쳐진 부분을 채워준다.(제일 위 다운받은 폰트를 적용시켜준다.)

다 적용 했으면 Font Atlas를 생성 후 Save 해준다.

 

3.만들어진 TMP_Font Asset Inspector에서 AtlasPopulation Mode가 Static을 되있는것을 Dynamic으로 변경하고 Sampling Point Size를 60정도로 설정후 Apply를 눌러적용 시킨다.

 

4.그리고 적용시킬 TMP 오브젝트에 폰트에셋을 적용시켜 사용한다.

 

Q.TMP 한글 폰트 적용까진 다했는데 스크립트에서는 어떡해 사용해?

A.

using UnityEngine;
using TMPro;

public class DebugText : MonoBehaviour)
{
    public TMP_Text tmpT;
    public TextMeshProUGUI tmpUgui;

    tmpT.text = "원하는 텍스트 적어넣기";
    tmpUgui.text = "원하는 텍스트 적어넣기2";
}

TMP_Text , TextMeshProUGUI 자료형을 선언하고 사용하면 된다. 

Q.비트연산자는 뭐야?

A.

컴퓨터가 나타내는 제일 작은 단위인 비트(bit = 0 또는 1)

비트 연산자는 bit(0,1)을 이동하거나 논리 연산을 할떄 사용하는 것이다.

논린 연산을 사용하면 이런식의 결과값을 얻을수 있다.

AND : 1000 & 0001 = 0000 
OR : 1000 & 0001 = 1001 
XOR : 1000 & 0001 = 1001 
NOT : 1000 -> 0111 

0010  << 1 = 0100 

0010  >> 1 = 0001

(<< , >> 이동시 처음과 끝 bit 는 0으로 채워진다.)

 

Q.비트연산자는 왜 쓰는거야?

A.

효율적이고 빠른 데이터 처리와 메모리 절약이 목적

Unity에서는 레이어마스크 처리 할 때 사용이 됨

 

사용하는 이유를 정확히 설명하면

1.메모리 효율성 :하나의 정수 값으로 각각의 비트를 개별적인 상태로 관리 할수 있기 때문에

예를 들이 32bit 정수 (4byte = int)는 32개의 서로 다른 상태를 한번에 표현 할 수 있기 때문

2.빠른 성능 : 비트연산은 CPU에 최적화 된 연산이며 덧셋,곱셈 보다 비트 연산이 더 빠르게 수행되기 떄문에

그래픽 처리시 매우 높은 최적화 효율을 보인다.

3.복잡한 조건을 단순하게 표현 : 메모리 효율성과 좀 겹치는 내용이지만, 32개의 정수 단일값으로 다중 상태를 관리하기에 가독성이 매우 좋아짐 

 

Q.레이어마스크는 뭐야?

A.

Unity의 레이어비트마스크(Bit Mask)를 사용하여 여러 레이어를 하나의 값으로 표현하는 방식

오브젝트의 레이어 설정 및 32개의 레이어(int 정수= 32bit)

 

 p.s 비트마스크?

bit의 이진수 표현을 활용하여 나타내는 방법

 

Q.레이어마스크는 왜 쓰는거야?

A.

일반적으로 내 공격이 나한테 맞거나 , 적의 공격이 적에 맞거나 하는건 게임에서는 적용되면 않된다.

(물론 적용되는 게임들도 있다) 이러한 문제를 대처하기 위해 레이어마스크를 사용하는것이다 .

 

1.충돌 검사 : 비트 마스크를 사용하여 특정 레이어에 속한 오브젝트만을 대상으로 충돌 검사 수행이 가능

즉, 태그,이름(문자열)로 검색할 필요가 없으니 최적화에 도움이 됨

2.레이캐스팅 제어 : 레이캐스트가 특정 레이어의 오브젝트에만 반응하도록 비트마스크를 설정 할 수 있음

 

예시1)

객체에서 레이(빛)을 쏴서 부딪히는 객체를 탐지할때 ,장애물 오브젝트는 무시하고 탐색을 하고 싶을때

해당 장애물의 레이어를 제외하여 레이가 해당 장애물 오브젝트에 부딪힐때 투과해서 쏠수 있게 하는기능

레이(빛)이 장애물(8번레이어)에 닿은경우 투과하여 사람(5번 레이어)에 접촉하여 사람 오브젝트의 Data를 얻게됨

해당 내용을 코드로 작성하면 

public int ObstcalseLayer = 5;
public int PeopleLayer = 8;

LayerMask layerMask = 1 << ObstcalseLayer; //장애물(5번레이어)를 비트마스크로 생성후 레이어마스크에 초기화
raycastObj = ~raycastObj; //반전비트를 이용하여 장애물 레이어를 0으로 변경
 
 //레이캐스트에 layerMask를 인자값으로 넣어 장애물 레이어를 투과하여 레이캐스트를 발사
 Physics.Raycast (transform.position, transform.TransformDirection (Vector3.forward), hit, Mathf.Infinity, layerMask);

이렇게 작성된다.

 

3.카메라 렌더링 설정 : 카메라가 특정 레이어의 오브젝트를 렌더링하도록 설정하여, 게임의 시각적 요소를 세밀하게 제어를 가능하게 한다. 

 

Q.레이어마스크를 어떡해 쓰는거야?

A.우선 오브젝트에 있는 레이어를 코드내에서 사용하기위해 비트마스크로 생성해야한다.

 

1 << n // 1을 n번째 비트 위치로 시프트(이동)한다는 의미
//즉 n은 레이어(0~32 layer정수값을 의미), n번쨰 레이어를 나타내는 비트마스크로 생성한다는 의미

//예시
//충돌된 객체의 게임오브젝트에서 레이어를 초기화
int layer =  collision.gameObject.layer;

1 << layer; //호출받아온 레이어를 비트마스크로 생성하여 사용

 

해당 코드처럼 1 << n 을 사용하여 비트마스크로 생성하는것이다. 

 

그럼 비트마스크로 생성까지 완료했으면 그 이후에는 어떡해 해야되나?

해당 비트마스크를 이용하여 검사를 진행하면된다.

 

위에서 설명한 논리 연산자들을 활용하는 것이다.

AND (&): 특정 레이어의 존재 여부 확인할떄 사용
OR (|): 새로운 레이어를 추가할 때 사용
XOR (^):  두 레이어의 차이를 찾을떄 사용 ( 비교하는 대상이 다르면 1 )
NOT (~):  특정 레이어를 제외시킬때 사용 ( 모든비트 반전 )

 

보통 이 논리연산자들 이 특징들을 사용한다. 

 

public LayerMask levelCollisonLayer; //비교할 기준 레이어

private void OnTriggerEnter2D(Collider2D collision)
{
	//LayerMask의 value 프로퍼티는 해당레이어의 비트마스크를 십진수를 반환해준다.
    if (IsLayerMatched(levelCollisonLayer.value, collision.gameObject.layer))
    {
        //레이어가 일치하면 true
    }
	//레이어가 불일치하면 false
}



private bool IsLayerMatched(int value, int layer)
{
	//value는 비트마스크이기 때문에 그대로 사용 , layer는 비트마스크로 생성 
    
    //(value | 1 << layer) : | 연산자로 기준 레이어(value)와 객체와 충돌한 레이어(layer) OR연산
    
    //OR 연산된 비트마스크 가 기준 레이어와 일치할경우 True
    return value == (value | 1 << layer);
}

 

해당 코드는 OR 연산을 충돌된 객체 레이어가 기준 레이어와 일치할 경우 동작하는 코드이다.

OR 연산은 레이어를 추가할때 사용 하는데 , 이런식으로도 원하지 않는 레이어는 제외할수 있게 구성 할 수도 있다.

 


해당 테크닉은 유니티 게임 개발시에 정말 많이 사용될것이기 때문에 반드시 익혀둬야한다.

 

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

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);
	}

}

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

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

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

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을 컴포넌트로 호출해서 사용하면된다.

+ Recent posts