해당 글은 Csv를 Json으로 변환하는 툴에 대한 가이드입니다.

자세한 코드 내용은 아래의 블로그를 참고해 주세요 

2025.02.25 - [Unity] - [Unity] Csv To Json 변환 툴

 

[Unity] Csv To Json 변환 툴

내일배움캠프-레벨업세션에서 엑셀 파일을 Json으로 변환하는 기능을 봤는데 이번 프로젝트에서 그대로 사용하기에는 맞지않다고 느껴서유니티 내부에서 csv파일을 Json으로 저장하는 기능을 만

youcheachae.tistory.com

 


Csv 변환 방법                                                                                       

  1. wrapper 클래스를 작성합니다
    1. 클래스는 반드시 ICsvParsable 인터페이스를 상속받아 구현해야 합니다.
    2. Parse(string [] values) 메서드 구현
      1. CSV 파일의 각 행은 values 배열로 전달됩니다(string 타입).
      2. 클래스의 필드명에 맞춰서 형변환이 필요합니다
        1. 구체적인 예시는 ItemWrapper의 Parse메서드 참고해 주세요

- 형 변환 방식

// enum 변환 방법
itemType = (변환할 Enum)Enum.Parse(typeof(변환할 Enum), 문자열);

// ex
ItemType itemType = (ItemType)Enum.Parse(typeof(ItemType), values[인덱스]);

// 숫자 변환 방법
//숫자 변환: 
int.Parse(values[인덱스];
float.Parse(values[인덱스])

 

- 각 행은 values 배열로 전달됩니다.

 

- 구체적인 예시

[System.Serializable]
public class ItemWrapper : ICsvParsable
{
    [SerializeField] private int itemNum;
    [SerializeField] private ItemType itemType;
    [SerializeField] private string itemName;
    [SerializeField] private string itemToopTip;
    [SerializeField] private float attackSpeed;
    [SerializeField] private float attackDamage;
    [SerializeField] private float durationTime;

    public void Parse(string[] values)
    {
        // [0] item num
        // [1] item type
        // [2] name
        // [3] tooltip
        // [4] attackSpeed
        // [5] attackDamage
        // [6] durationTime
        // [7] PlayerState

        itemNum = int.Parse(values[0]);
        itemType = (ItemType)Enum.Parse(typeof(ItemType), values[1]);
        itemName = values[2];
        itemToopTip = values[3];
        attackSpeed = float.Parse(values[4]);
        attackDamage = float.Parse(values[5]);
        durationTime = float.Parse(values[6]);
    }
}

 

 

2. Resources 폴더에 CSV 파일 추가

  • CSV 파일명은 wrapper 클래스명과 정확히 동일해야 합니다.
  • 예시 csv 파일

ItemWrapper.csv
0.00MB

 

 

3. 변환 실행

  1. 씬의 Converter 오브젝트에서 CsvToJsonConverter 컴포넌트의 className 배열에 클래스명(=CSV 파일명)을 입력합니다.
  2. Inspector에서 [Csv to Json] 버튼을 클릭합니다.
  3. 변환이 완료되면 콘솔에 성공 메시지가 표시됩니다.
  4. JSON 파일은 Application.persistentDataPath 경로에 저장됩니다.
    1. 구제적인 경로
C:\Users\사용자이름\AppData\LocalLow\DefaultCompany\프로젝트이름

 

저장된 Json 불러오기                                                                                     

  • 저장된 클래스를 역직렬화, 리스트로 가져오기
// 저장된 클래스를 역직렬화
List<클래스명> 변수이름 = JsonSerialized.Deserialization<클래스명>("클래스명")

// 사용 예시 
List<ItemWrapper> itemWrapperList = JsonSerialized.Deserialization<ItemWrapper>("ItemWrapper");

 

!!!  주의사항 !!!                                                                                                 

  • CSV 파일, wrapper 클래스, className 배열에 입력한 이름이 모두 일치해야 합니다.
  • CSV 파일은 반드시 Resources 폴더 하위에 위치해야 합니다.
  • CSV 파일이 비어있거나 형식이 잘못된 경우 오류가 발생합니다.

깃허브

https://github.com/kimYouChae/UnityCsvToJsonConverter

 

GitHub - kimYouChae/UnityCsvToJsonConverter

Contribute to kimYouChae/UnityCsvToJsonConverter development by creating an account on GitHub.

github.com

 

유니티에서 스크립트를 생성하면, Monobehavior을 기본적으로 상속받고 있다.

이러한 Monobehavior 클래스는 어떤 역할을 하는 것일까?

 

📝 Monobehavior                                                                                                         

유니티 공식 docs에서는 아래와 같이 설명하고 있다.

MonoBehaviour 클래스는 대규모 이벤트 메시지 컬렉션에 대한 액세스를 제공하며, 이를 통해 현재 프로젝트에서 발생하는 상황에 따라 코드를 실행할 수 있습니다.

 

🔖 Monobehavior의 역할

 

1. 모든 유니티 스크립트가 파생되는 기본 클래스

 

2. 에디터와 스크립트 파일을 연결시켜주는 역할

클래스를 에디터에서 읽고, 컴포넌트로 추가하는 등의 동작을 하기 위해서는 유니티 에디터가 클래스에 대한 정보를 알아야 한다. 이때 Monobehavior이 그 역할을 해준다.

 

3. 시작 및 업데이트 등과 같은 대한 연결을 제공 

Start(), Update(), OnEnable() 등의 콜백 함수를 의미한다.

 

4. 이러한 이벤트 함수들의 생명주기 관리

 

🔖 Monobehavior의 특징

1. 게임오브젝트의 Component로 등록해야 사용가능

반대로, Monobehavior을 상속받지 않는 클래스는 게임오브젝트의 컴포넌트로 등록할 수 없다.

 

2. Monobehavior을 상속받은 클래스를 사용하기 위해서는 GetComponent로 클래스를 가져와야 함

 

3. new로 동적 생성이 불가능 

 

🔖 콜백 함수(생명주기 메서드)는 어떻게 호출되는 것인가?

유니티에는 Messaging System이라는 시스템이 있는데 특정 시점에 유저가 정의한 함수가 동작할 수 있게 해 준다.

Monobehavior 스크립트가 처음으로 이용될 때, 스크립트 내 이러한 함수들이 있다면

이 스크립트는 각 함수와 관련된 List에 저장된다. 

예를 들어서 어떤 스크립트에 Update 함수가 정의되어 있다면 이 스크립트는 Update와 관련된 List에 저장되고,

이 List는 매 프레임마다 Update가 호출되어야 할 시점에 사용된다.

 

사용하지 않는 " 자주 호출되는 메서드(특히 Update, FixedUpdate, LateUpdate) 관련된 함수들은 사용하지 않으면 지우는 것이 성능에 좋다"라는 이야기를 들어보았을 것이다.

이것은 Monobehavior가 이용될 때 해당 메서드를 사용하던 안 하던 실행하기 때문이다.

 

자세한 내용은 공식문서를 참고하자! 

https://unity.com/kr/blog/engine-platform/10000-update-calls

 

10000번의 Update() 호출

void Update() { transform.Translate(0, 0, Time.deltaTime); }하지만 숙련된 개발자는 위의 코드에서 몇몇 의문이 들 수 있습니다. 이 함수는 도대체 언제 호출되지? 만약, 여러개의 스크립트가 있고 그 스크립

unity.com

 

 

내부적으로는 IL2CPP와 같은 Unity의 스크립팅 백엔드에서 MonoBehaviour가 어떻게 처리되는지 알 수 있다.

 

🔖 Monobehavior이 콜백함수를 실행하는 순서, 생명주기

Awake() → OnEnable() → Start() → Update() → LateUpdate() → OnDisable() → OnDestroy() 등의 순서로 호출된다.

자세한 내용은 유니티 생명주기 공식문서를 참고하자

 


유니티 라이프사이클

https://docs.unity3d.com/kr/2019.4/Manual/ExecutionOrder.html

 

이벤트 함수의 실행 순서 - Unity 매뉴얼

Unity 스크립트를 실행하면 사전에 지정한 순서대로 여러 개의 이벤트 함수가 실행됩니다. 이 페이지에서는 이러한 이벤트 함수를 소개하고 실행 시퀀스에 어떻게 포함되는지 설명합니다.

docs.unity3d.com

 

 

유니티 중요 클래스 - Monobehavior

https://docs.unity3d.com/kr/2020.3/Manual/class-MonoBehaviour.html

 

중요 클래스 - MonoBehaviour - Unity 매뉴얼

MonoBehaviour 클래스는 기본적으로 모든 Unity 스크립트가 파생되는 기본 클래스입니다. Unity의 프로젝트 창에서 C# 스크립트를 생성하면 MonoBehaviour에서 자동으로 상속되며, 템플릿 스크립트를 제공

docs.unity3d.com

 

 

1인칭 플레이어 움직임을 만들 때 근본적인 문제점이 발생했다. 

플레이어가 어떻게 자연스럽게 경사를 올라가게 할 것인가? 

일반적인 애니메이션과 콜라이더를 사용하여 경사를 올라가면, 발이 다른 오브젝트의 매쉬를 뚫거나 발이 공중에 떠있게 될 것이다

실제로 경사를 올라갈 때 앞에 있는 발은 매쉬를 뚫고 들어가고, 뒤에 있는 발은 공중에 뜨게된다.

 

이를 해결하려면, 발에서 Ray를 쏴서 바닥과 발 사이의 각도를 구한 다음 발을 회전시켜야 할까?라고 생각하고 검색을 해보던 도중에, IK 기능을 발견했다. 

 

유튜브에 올라와 있는 강의를 참고해서 작성하였다

https://www.youtube.com/watch?v=rGB1ipH6DrM&t=133s

 


📝IK?                                                                                                           

 

일단. IK가 무엇인지 알아야 한다. 유니티 공식문서에 따르면 , 대부분의 애니메이션은 스켈레톤의 조인트 각도를 미리 정해진 값으로 회전하여 만든다. 자식 뼈대 (조인트)는 부모 뼈대 (조인트)의 회전에 따라 변하고, 이런 방식을 순 운동학 (FK)라고 한다.

 

하지만 이런 방법을 반대로 바라본다면 더욱 유용하다. 오브젝트를 기준으로 뼈대 (조인트) 값을 계산하여 적용하는 것이다. 예를 들어 플레이어가 다른 오브젝트를 건드리거나, 울퉁불퉁한 표면 위에 캐릭터의 두 발이 자연스럽게 밀착해 있도록 할 수 있다. 이러한 접근법을 역운동학 (IK)라고 한다. 

 

유니티 공식문서에 있는 IK 관련 이미지

 

IK를 적용하기 위해서는 아바타의 타입이 휴머노이드여야 한다.  


📝 사전준비                                                                                                           

1. 플레이어의 Animator의 Layer의 설정에서 IK Pass 옵션을 체크해준다 

2. 플레이어가 걸어갈 발판/경사로 등의 레이어를 Walkable로 지정해 준다 

 


📝구현                                                                                                            

PlayerAnimator.cs

public class PlayerAnimator : MonoBehaviour
{
    [Header("===Animator===")]
    [SerializeField] private Animator animator;

    [Range(0, 1f)]
    public float distanceToGround;

    public LayerMask layerMask;
    
    void Start()
    {
        animator = GetComponent<Animator>();
    }
    
    private void OnAnimatorIK(int layerIndex)
    {
    
    }
}

  void OnAnimatorIK(int layerIndex)

             애니메이션 IK(역운동학)을 설정하기 위한 콜백

              Animator Component가 내부 IK 시스템을 업데이트하기 직전에 호출

             콜백 안에 IK 목표 위치와 , 가중치를 설정하는 데 사용한다

 

private void OnAnimatorIK(int layerIndex)
    {
        // 애니메이터가 실행되는 매 프레임 실행
       if(animator != null) 
        {
            animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);
            animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1f);
            animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1f);
            animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1f);

animator.SetIKPositionWeight()

            공식문서에서는, IK 목표의 변환 가중치를 설정한다. ( 0 = IK전 원래 애니메이션에서 1 = 목표에서 )

            즉 가중치를 설정하여 원래 애니메이션과 , IK 조정 사이의 혼합정도를 결정한다.

  animator.SetIKRotationWeight()

            SetIKPositionWeight과 같이 회전에 대한 가중치를 설정한다.

🔖 가중치
가중치가 0일 때 : 원래 애니메이션이 100% 적용된다. IK는 전혀 영향을 주지 않는다
가중치가 1일 때 : 조정이 100% 적용된다. 원래 애니메이션은 무시된다
가중치가 0 ~ 1 사이일 때 : 원래 애니메이션과 IK 조정값이 해당 비율로 혼합된다

 

🔖  여기서 AvaterIkGoal은 Enum으로,
LeftFoot과 RightFoot은 Animator에 적용된 아바타의 
Left Leg의 Foot과 , Right Leg의 Foot를 의미한다. 

 

 

 

 // left foot 
 RaycastHit hit;
 Ray ray = new Ray(animator.GetIKPosition(AvatarIKGoal.LeftFoot) + Vector3.up, Vector3.down);
 
 Debug.DrawRay(ray.origin, ray.direction * (distanceToGround + 1f), Color.red);

 Ray 생성

             시작지점 :  아바타의 LeftFoot의 위치에서 1f 올라간 곳, 

             방향 : 시작지점에서 아래쪽 방향

 

  DrawRay로 레이를 그리면, 주황색 동그라미에서 시작하여 아래쪽으로 ray를 쏘고 있는 것을 확인할 수 있다. 

 

if (Physics.Raycast(ray, out hit, distanceToGround + 1f , LayerManager.Instance.IgnorePlayerLayer)) 
{

    if(hit.transform.gameObject.layer == LayerManager.Instance.WalkableLayerInt) 
    {
    	// 추가예정
    	// 플레이어 발 위치 , 회전 적용
    }
}

  Raycast

             플레이어를 제외한 오브젝트를 감지해야 하기 때문에 PlayerLayer을 제외한 레이어만 검사

             레이캐스트가 성공적으로 충돌체를 감지하면 , if문 실행 

 

해당 충돌체의 레이어가 'Walkable'이면?

             플레이어 발의 위치와 회전을 변경


(+) 레이어에 관해서 

layer을 검사할 땐 layerMask의 인덱스 값으로 검사해야 하기 때문에 GetMask를 사용해 준다

해당코드는 어디에 있던지 상관없다

[SerializeField] private LayerMask ignorePlayerLayer;   // 플레이어 레이어를 제외한 레이어
[SerializeField] private int walkableLayerInt;

void Start()
{
    walkableLayerInt = LayerMask.NameToLayer("Walkable");
}

PlayerLayer을 뺀 LayerMask 정보는 인스펙터 창에서 PlayerLayer을 제외한 다른 레이어들을 체크해주면 된다.


 

if (Physics.Raycast(ray, out hit, distanceToGround + 1f , LayerManager.Instance.IgnorePlayerLayer)) 
{
    if(hit.transform.gameObject.layer == LayerManager.Instance.WalkableLayerInt) 
    {
        Vector3 footPosition = hit.point;
        footPosition.y += distanceToGround;
        animator.SetIKPosition(AvatarIKGoal.LeftFoot, footPosition);
        animator.SetIKRotation(AvatarIKGoal.LeftFoot, Quaternion.LookRotation(transform.forward, hit.normal));
    }
}

 해당 충돌체의 레이어가 'Walkable'이면?

            발의 위치

                        hit.point(레이가 충돌한 정확한 지점)를 footPosition으로 사용

            footPosition.y += distanceToGround

                       발이 지면에 완전히 묻히지 않고 자연스럽게 위치하도록 약간의 오프셋을 주기 위함

                       현재 distanceToGround는 0.06f 정도로 설정되어있다

             발의 회전

                         Quaternion.LookRotation을 사용해서 플레이어가 바라보고 있는 방향으로,

                         hit의 법선 벡터와 일치하도록 hit.normal을 사용해서 회전값을 줌

 

전체코드

public class PlayerAnimator : MonoBehaviour
{
    [Header("===Animator===")]
    [SerializeField] private Animator animator;

    [Range(0, 1f)]
    public float distanceToGround;	// 0.06f

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    #region Foot Ik
    

    private void OnAnimatorIK(int layerIndex)
    {
        // 애니메이터가 실행되는 매 프레임 실행

        if(animator != null) 
        {
            animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);
            animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1f);
            animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1f);
            animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1f);

            // left foot 
            RaycastHit hit;
            Ray ray = new Ray(animator.GetIKPosition(AvatarIKGoal.LeftFoot) + Vector3.up / 2, Vector3.down);

            Debug.DrawRay(ray.origin, ray.direction * (distanceToGround + 1f), Color.red);

            if (Physics.Raycast(ray, out hit, distanceToGround + 1f , LayerManager.Instance.IgnorePlayerLayer)) 
            {
                if(hit.transform.gameObject.layer == LayerManager.Instance.WalkableLayerInt) 
                {
                    Vector3 footPosition = hit.point;
                    footPosition.y += distanceToGround;
                    animator.SetIKPosition(AvatarIKGoal.LeftFoot, footPosition);
                    animator.SetIKRotation(AvatarIKGoal.LeftFoot, Quaternion.LookRotation(transform.forward, hit.normal));
                }
            }

            // 오른발
            ray = new Ray(animator.GetIKPosition(AvatarIKGoal.RightFoot) + Vector3.up, Vector3.down);

            if (Physics.Raycast(ray, out hit, distanceToGround + 1f, LayerManager.Instance.IgnorePlayerLayer))
            {              
                if (hit.transform.gameObject.layer == LayerManager.Instance.WalkableLayerInt)
                {

                    Vector3 footPosition = hit.point;
                    footPosition.y += distanceToGround;
                    animator.SetIKPosition(AvatarIKGoal.RightFoot, footPosition);
                    animator.SetIKRotation(AvatarIKGoal.RightFoot, Quaternion.LookRotation(transform.forward, hit.normal));
                }
            }
        }
    }
    
    #endregion

 


📝 결과                                                                                                              

IK를 적용하기 전 모습

IK를 적용한 후

 


 

 OnAnimatorIK

https://docs.unity3d.com/kr/530/ScriptReference/MonoBehaviour.OnAnimatorIK.html

 

MonoBehaviour-OnAnimatorIK(int) - Unity 스크립팅 API

Callback for setting up animation IK (inverse kinematics).

docs.unity3d.com

 

SetIKPositionWeight

https://docs.unity3d.com/kr/530/ScriptReference/Animator.SetIKPositionWeight.html

 

Animator-SetIKPositionWeight - Unity 스크립팅 API

Sets the translative weight of an IK goal (0 = at the original animation before IK, 1 = at the goal).

docs.unity3d.com

 

역운동학 (IK)

https://docs.unity3d.com/kr/530/Manual/InverseKinematics.html

 

역운동학(IK) - Unity 매뉴얼

대부분의 애니메이션은 스켈레톤의 조인트 각도를 미리 정해진 값으로 회전하여 만듭니다. 자식 조인트의 포지션은 부모의 회전에 따라 변하므로 조인트 체인의 끝 점은 체인에 포함된 각 조인

docs.unity3d.com

 


깃허브

https://github.com/kimYouChae/Sparta_OnlyUp

 

 

New Input System과 기본적인 플레이어 움직임에 관한 글은 아래 블로그를 참고

2025.03.04 - [Unity] - [Unity] New Input System 으로 플레이어 움직이기

 

[Unity] New Input System 으로 플레이어 움직이기

1. New Input System2. New Input System 사용방법3. PlayerController  스크립트4. PlayerInput 컴포넌트📝 New Input System이란?                                                                          Unit

youcheachae.tistory.com

 


📝 플레이어 Jump                                                                                    

 

상단의 링크에서 작성한 PlayerMove 스크립트에서 추가로 작성해주었다.

    // ....
    #region 플레이어 점프

    public void OnJump(InputAction.CallbackContext context) 
    { 
        // 한번 눌리면 
        if(context.phase == InputActionPhase.Started) 
        {
            playerRb.velocity = Vector3.zero;
            playerRb.AddForce(Vector3.up * 5f , ForceMode.Impulse);
        }
    }
    #endregion
    // ....

  OnJump()

             PlayInput컴포넌트에서 입력 발생 시 리턴되는 콜백을 받아오기 위한 함수 작성

             매개변수를 InputAction.CallbackContext로 콜백을 받아온다

             입력이 눌렸을 때, 플레이어의 Vector3.Up방향(0,1,0)으로 , Addforce 해준다. 

 

  컴포넌트의 Event → Player  Jump Aciont에 (+) 버튼으로 추가해 준다

  Player 오브젝트를 드래그해서 넣고, PlayerController스크립트의 OnJump메서드를 추가한다 

 

📝 발생한 문제                                                                                    

열심히 스페이스바를 누르고 있지만 플레이어에 Addforce가 적용되지 않는 모습이다.

 

🔖 해결시도

1. Mass를 줄이기 / 늘리기 

            Mass는 오브젝트 질량 이기 때문에 질량을 줄이면 느리게 떨어지고, 늘리면 빨리 떨어질 거라 생각했다.

:

하지만 Mass를 변경해도 떨어지는 속도가 변하지 않았다. 

 

2. 중력 Scale을 수정 

             Edit → Project Setting Physics의 Gravity를 -24 정도까지 줄여봤다. 

: 하지만 이것 또한 플레이어가 떨어지는 속도가 변하지 않았다. 

 

3. 테스트로 만들어놓은 큐브와 컴포넌트 하나하나 비교해 보기....

: 테스트용 큐브와 플레이어의 차이는 PlayerController밖에 없는데,

혹시 여기서 문제인가...?! 싶어서 코드를 다시 뜯어봤다.

 

 

- 그 당시 PlayerMove() 코드 

private void PlayerMove() 
{
    Vector3 dir = transform.forward * moveVector.y + transform.right * moveVector.x;
    dir *= speed;
    playerRb.velocity = dir;
}

: 해당 코드에서 , y값의 속력을 지정해 주는 부분이 없다!!

그래서 y의 속력이 항상 0으로 설정되고 , 그래서 Gravity값과 Mass를 아무리 수정해도 변화가 없었던 것.............

 

- 해결 방법

🔖 dir의 y는 현재 물리엔진에서 계산되고 있는 중력이나 점프 등으로 발생한 y 방향의 속도를 유지하기 위해서 playerRB.velocity.y로 설정해 주면 된다.

    dir.y = playerRB.velocity.y;

 

y 속도를 생각하지 못한 내 실수였던 것....


: 상단의 링크에서 작성한 코드 PlayerMove()에 대해 추가적인 설명을 하고 넘어가려 한다.

전체적인 코드는 상단의 링크를 참고!

private void Move() 
{
    Vector3 dir = transform.forward * curMoveInput.y + transform.right * curMoveInput.x;
    dir *= _moveSpeed;
    dir.y = playerRB.velocity.y;    

    playerRB.velocity = dir;
}

  curMoveInput은 Vector2 타입으로, x와 y 밖에 없다.

 Vector2에서 움직일 때, 플레이어 기준 앞 / 뒤로 움직이면 y 값이 변화한다. 좌 / 우로 움직이면 x 값이 변화한다.

              y가 양수면 앞쪽으로 이동
              y가 음수면 뒤쪽으로 이동
              x가 양수면 오른쪽으로 이동
              x가 음수면 왼쪽으로 이동

 Vector3에서 움직일 때, 플레이어 기준 앞 / 뒤로 움직이면 z 값이 변화한다. 좌 / 우로 움직이면 x 값이 변화한다.

 

위에서 바라봤을 때 Vector2와 Vector3의 축 변화

 

  플레이어 기준 앞/뒤 움직임이 발생하면 플레이어 앞쪽(forward)과 y값을 곱하고, 좌/우 움직임이 발생하면 플레이어의 오른쪽 (right)과 x 값을 곱해서 가야 할 방향을 나타낸다.

  y는 위에서 설명한 대로, 현재 물리엔진에서 계산되고 있는 중력이나 점프 등으로 발생한 y 방향의 속도를 유지하기 위해서 playerRB.velocity.y로 설정해 주면 된다.

 

📝 마우스에 따른 플레이어 회전 / 카메라 회전                                                                               

접근방식

마우스를 회전하면, 플레이어의 x회전과 y 회전을 변경시켜야 된다고 생각했다. 

하지만 플레이어의 x값 회전을 하면 플레이어의 콜라이더가 땅에 박히거나 충돌 나는 문제가 발생할 것 같았다. 

: 콜라이더가 땅에 박힌다....


즉 , 마우스를 회전하면 

1. 플레이어의 y 회전 값 (좌, 우)이 변경되어야 한다.

2. 카메라의 x 회전 값 (상, 하)이 변경되어야 한다.

 

 

- 구체적인 코드

[Header("===Rotate===")]
[SerializeField] private Vector2 mouseDelta;        // 마우스 움직임 델타
[SerializeField] private float currentY;            // 현재 회전 상태 Y
[SerializeField] private float rotationBoundary = 80f;
[SerializeField] private float sensitivity;

private void LateUpdate()
{
    RotateCamera();
}

private void RotateCamera()
{
    // 플레이어는 rotation의 y값만 바껴야한다
    // 카메라는 rotation의 x값만 바껴야 한다

    currentY += mouseDelta.y * sensitivity;

    float newY = Mathf.Clamp(currentY, -rotationBoundary , rotationBoundary) ;

    // 카메라 회전 
    cameraTrs.localEulerAngles = new Vector3( -newY, 0, 0);

    // 플레이어 회전 
    transform.eulerAngles += new Vector3(0, mouseDelta.x * sensitivity, 0);
}

public void OnRotateCamera(InputAction.CallbackContext context)
{
    // Delta값 :
    // 화면의 중앙을 (0, 0) 기준으로
    // 마우스를 빠르게 움직일수록 절대값이 커짐
    //      마우스를 오른쪽으로 빠르게 이동: (15, 0)
    //      마우스를 왼쪽으로 천천히 이동: (-2, 0)
    mouseDelta = context.ReadValue<Vector2>();
    // Debug.Log($"Mouse Delta: {mouseDelta}");
}

  OnRotateCamera()

             context.ReadValue <Vector2>()로 마우스 움직임에 관한 Vector2 값을 mouseDelta 변수에 저장한다. 

  RotateCamera()

            카메라 회전을 계산하기 위해서 입력받은 mouseDelta값의 y에 sensitivity값을 곱해서 저장한다.

            카메라 회전 (위, 아래)는 너무 큰 값이 들어가면 360도 돌 수 있기 때문에 임시로 -80 ~ 80까지 값으로 설정했다.

            localEulerAngles를 사용해서 회전값을 적용한다. 

                      □ 카메라는 현재 Player 하위에 있기 때문에 local 회전값을 변경시켜 준다 

                       카메라의 회전값을 변경하면 (-) 값으로 들어가서 코드상에서 (- ) 값을 넣어준다

            플레이어 회전 (좌, 우)는 mouseDelta값의 x로 설정한다.

 


📝 플레이어 움직임/점프/카메라 회전 결과 

원하던 움직임이 잘 나타나는 것을 확인할 수 있다. 


https://github.com/kimYouChae/Sparta_OnlyUp

 

 

 

 

1. New Input System
2. New Input System 사용방법
3. PlayerController  스크립트
4. PlayerInput 컴포넌트

📝 New Input System이란?                                                                          

Unity에서 2019 버전부터 새로 만든 Input 시스템

 

🔖 기존 Input System과 다른 점

기존의 Input시스템에는 Axes를 추가하고, 스크립트를 작성할 때 Update에서 계속 GetKeyDown으로 검사해줘야 한다.

또한 플랫폼별 입력 대응이 어렵다는 한계가 있다. 

 

🔖 New Input System의 장점

1. 키보드, 컨트롤러 등의 여러 플랫폼에 쉽게 대응할 수 있다.

2. 하나의 코드를 통해 다양한 플랫폼에서의 입력 처리 가능

3. 입력 Action에 대한 함수를 직관적이고 간편하게 연결할 수 있다

 

기존 방식의 한계 

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        Jump();
    }
}

 

New Input System의 함수 연결 

void OnJump(InputAction.CallbackContext context)
{
    Jump();
}

  코드 가독성이 향상됨 

  입력 관리를 효율적으로 관리할 수 있다


📝 New Input System 사용                                                                                             

 

1. Package Manger의 InputSystem을 다운로드한다

 

 

2. Edit → Project Setting Player ActiveInputHandiling에서 사용할 Input System을 설정할 수 있다.

Input System Package(New)를 클릭하면 경고창이 뜨는데 Apply를 누른 후 유니티프로젝트를 재실행한다.

경고창 , Apply를 누른다

 

 

3. Create → Input Action을 생성한다. 이름은 PlayerInput으로 설정한다. 

 

 

4. 생성 후 Inspector 창에서 Edit Asset을 클릭한다

 

 

5. InputAction 창이 뜨면 왼쪽 상단에 Scema를 설정해 준다.

 

 

🔖 Control Scheme (컨트롤 스키마)
: 특정 플랫폼에서 사용할 입력 설정의 그룹
: 예시 
   : 키보드 + 게임패드 스키마
   : 키보드 + 마우스 스키마
: 플랫폼별 입력 대응을 할 수 있는 부분 

🔖 Action Maps (액션 맵)
: 사용할 키 입력에 대한 Action들을 그룹화

🔖 Actions (액션)
: 특정 입력에 대한 입력 동작 
: Started , Performed, Cancled등 다양한 입력상태 처리 가능 

 

 

 

6. Actions 추가 

플레이어 움직임 입력을 위한 Action을 추가한다.

Action Type은 Value / ControlType은 Vector2로 설정한다 

🔖 Action Type 
버튼 : 단일성으로 누르고 떼는 용도
Value : 지속적으로 값을 변화하고자 할 때

 

6-1. Move Action옆의 + 버튼을 누른 후 "Add Up ~ Composite"를 클릭한다

Up / Down / Left / Right에 해당하는 키를 바인딩해준다. 각각 w s a d로 바인딩해주었다. 

 

7. 다른 행동에 대한 Action을 추가해 준다

  마우스 움직임에 따른 카메라 회전 입력을 위해서 Look이라는 Action을 추가한다

             Action Type은 Value / ControlType은 Delta / Binding path는 Delta[Mouse]로 바인딩해준다 

  점프, 인벤토리, 상호작용 입력을 위한 Jump , Inventory, Interaction이라는 Action을 추가한다

             각각 Action Type은 Button / Binding path는 Backspace,  Tab , E [Keyboard]로 바인딩해준다

  공격키 입력을 위한 Attack이라는 Action을 추가한다

             Action Type은 Value / Binding path는 Left Button [Mouse]로 바인딩해준다 

 

📝PlayerController 스크립트                                                              

public class PlayerController : MonoBehaviour
{
    [Header("===Movement==")]
    private float _moveSpeed = 3f;
    private Vector2 curMoveInput;
    private void 

    FixedUpdate()
    {
        Move();
    }

    private void Move() 
    {
        Vector3 dir = transform.forward * curMoveInput.y + transform.right * curMoveInput.x;
        dir *= _moveSpeed;
        dir.y = playerRB.velocity.y;    

        playerRB.velocity = dir;
    }

    public void OnMove(InputAction.CallbackContext context) 
    {
        // 입력이 있으면 받아오기
        if (context.phase == InputActionPhase.Performed)
        {
            curMoveInput = context.ReadValue<Vector2>();
        }
        // 입력이 없으면 0,0 으로
        else if (context.phase == InputActionPhase.Canceled) 
        {
            curMoveInput = Vector2.zero;
        }
    }
}

   OnMove()

             PlayInput컴포넌트에서 입력 발생 시 리턴되는 콜백을 받아오기 위한 함수 작성

             매개변수를 InputAction.CallbackContext로 콜백을 받아온다

             현재 움직임에 대한 Vector2 타입의 curMoveInput 값을 들어온 콜백의 Vector2로 설정한다

 

   Move()

              받아온 curMoveInput을 사용하여 플레이어 움직임을 제어한다 

🔖InputActionPhase.Performed
: 콜백이 발생하는 동안 

🔖InputActionPhase.Started
: 콜백이 발생할 때, 1회

🔖InputActionPhase.Canceled
: 콜백이 종료될 때 , 1회 

 

📝 PlayerInput 컴포넌트                                                               

플레이어 오브젝트를 만든 후 PlayerController스크립트와 Player Input 컴포넌트를 추가해 준다 

Acions에 방금 만든 PlayerInput을 추가해 준다 

Behavior도 Invoke Unity Events로 설정한다

 

🔖 Invoke Unity Events
: 유저가 정의한 Action에 대한 키 입력이 들어오면 , 설정한 이벤트 (함수)가 실행된다 

 

컴포넌트의 Event → Player  Move Aciont에 (+) 버튼으로 추가해 준다

Player 오브젝트를 드래그해서 넣고, PlayerController스크립트의 OnMove메서드를 추가한다 

 

📝 결과                                                               

: wsad가 입력되면 플레이어가 움직이는 것을 확인할 수 있다. 

 


 

📝 사전작업 

: Window → PackageManager → TextMeshPro를 다운로드한다

 

📝  사용할 폰트를 Asset폴더에 추가한다

 

(+) 눈누 사이트에 가면 상업적으로 사용한 폰트를 찾을 수 있다

https://noonnu.cc/

 

눈누

상업용 무료 한글 폰트 사이트

noonnu.cc

 


📝  폰트 에셋 추가방법

  window → TextMeshPro → Font Asset Creator

 

 

각 맞게 입력 후 Generate Font Atlas 클릭, 우측에 폰트가 생성된 걸 볼 수 있다.

하단의 Save를 클릭해서 폰트 에셋을 저장 후 사용하자 

□ 프로퍼티 

             Source Font File -  Asset 폴더에 드래그한 폰트 파일

             Sampling Point Size - Auto Sizing

             Padding - 5 

             Packing Method : Fast

             Atlas Resourlution : 4096 , 4096 

             Character Set : Custom Range

 

             Character Sequence : 32-126,44032-55203,12593-12643,8200-9900 입력

            32-126 : 영어 (알파벳 범위)

            44032 - 55203 : 한글범위 / 받침이 있는 모든 글자 

            12593 - 12643 : 자음 모음이 하나씩 떨어지 있는 글자 ex) ㄱ ㄴ ㄷ ㅏ ㅖ

            8200 - 9900 : 특수문자 


Font Asset Creator 공식문서

 

https://docs.unity3d.com/kr/2022.3/Manual/UIE-font-creator-properties.html

 

Font Asset Creator 프로퍼티 레퍼런스 - Unity 매뉴얼

Font Asset Creator로 폰트 아틀라스를 생성하고 업데이트할 수 있습니다.폰트 아틀라스를 생성하고 업데이트하려면 폰트 에셋의 인스펙터(Inspector) 창에서 Update Atlas Texture를 선택합니다.

docs.unity3d.com

 

1. Audio 컴포넌트
2. Audio 플레이
3. 오디오 볼륨 조절 슬라이더

📝 1. Audio컴포넌트                                                                                                                                                       

🔖 Audio Clip

  • 재생할 수 있는 오디오 데이터
  • 유니티는. aif,. wav,. mp3, 및. ogg 형식을 지원함

 

🔖 Audio Souce

  • 씬에서 오디오를 재생할 수 있는 컴포넌트
  • 2D/3D 사운드 설정 가능 

 

🔖 AudioMixer 

  • 오디오 소스에 대한 제어, 균형 및 동적 조정을 제공
  • 그룹화를 통한 카테고리별 사운드 관리 가능 (BGM, SFX, Voice 등)

 

📝2. Audio 플레이                                                                                                                                   

 

0. 오디오를 "듣기"위해서 Main Camera에 AudioListener 컴포넌트를 달아준다

 

1. 오브젝트에 AudioSource 컴포넌트를 추가한다

AudioSource 필드에 오브젝트의 AudioSource  컴포넌트를 할당한다

AudioClip필드에 실행할 오디오 클립을 할당한다

 

2. BGMPlayer.cs

public class BGMPlayer : MonoBehaviour
{
    public AudioSource source;
    public AudioClip clip;

    void Start()
    {
        source = GetComponent<AudioSource>();
        
        // 1. Play 함수
        source.Play();

        // 2. PlayOnShot 함수
        source.PlayOneShot(clip);
    }

}

AudioSource.Play()

              해당 소스에 할당된 AudioClip을 처음부터 재생

              매개변수가 없음 (AudioSource 컴포넌트에 AudioClip이 미리 할당되어야 함)

AudioSource.PlayOneShot(AudioClip)

              한 번에 여러 오디오를 겹쳐 재생할 수 있음

              재생시킬 오디오 clip을 매개변수로 받음

              같은 소스에서 여러 효과음을 동시에 재생할 때 유용함

 

3. 실행

실행하면 오디오가 1회 실행되는 것을 확인할 수 있다

혹시 들리지 않는다면 게임화면의 소리가 음소거되어있는지 확인하자 

 

📝3. 오디오 볼륨 조절 슬라이더                                                                                                                                     

1. Create →  Audio Mixer를 클릭하여 오디오믹서를 만든다

 

2. 우측 Groups에서 (+) 버튼을 눌러 그룹을 만든 뒤 이름을 BGM과 SFX로 지정한다

 

3. Asset폴더에 생성되어 있는 AudioMixer하위의 Master을 클릭한 후 

인스펙터 창의 Volume 위에서 우클릭

→ Expose 'Volume (of Master) to script"를 클릭

 

🔖 스크립트로 Audio Mixer의 Master / BGM / SFX 그룹에 접근하기 위해서 Expose를 해줘야 한다.

  볼륨 옆에 → 화살표가 뜨면 된 것이다

 

4. AudioSource가 할당되어 있는 오브젝트의 AudioSource → Output에 만든 AudioMixer의 그룹을 할당해 준다

BGM을 실행하는 오브젝트의 AudioSource뿐만 아니라 SFX를 실행하는 AudioSource에도 Output을 설정해 준다 

 

5. AudioMixer의 파라미터 설정

해당 그룹에 맞는 파라미터로 이름을 수정해 주면 된다

왼쪽에 흐릿한 글씨로 Volume(of BGM)를 확인한다. BGM이면 AudioSource의 Output이 "BGM"인 경우에 해당한다. 

 

6. AudioSlider.cs

using UnityEngine.Audio;
using UnityEngine.UI;

public class AudioSlider : MonoBehaviour
{
    public AudioMixer audioMixer;

    public Slider MasterSlider;
    public Slider BGMSlier;
    public Slider SFXSlider;

    private float minVolume = -60f;
    private float maxVolume = 10f;
    
    
    void Start()
    {
        MasterSlider.onValueChanged.AddListener(SetMasterVolume);
        BGMSlier.onValueChanged.AddListener(SetBGMVolume);
        SFXSlider.onValueChanged.AddListener(SetSFXVolume);

    }
    public void SetMasterVolume(float volume)
    {
        audioMixer.SetFloat("Master", Mathf.Clamp(volume * 100 - 80 , minVolume , maxVolume));
    }

    public void SetBGMVolume(float volume)
    {
        audioMixer.SetFloat("BGM", Mathf.Clamp(volume * 100 - 80 , minVolume , maxVolume));
    }

    public void SetSFXVolume(float volume)
    {
        audioMixer.SetFloat("SFX", Mathf.Clamp(volume * 100 - 80 , minVolume , maxVolume));
    }

}

Slider.onValueChanged.AddListener()

               슬라이더에서 값 수치가 변경될 경우에 실행됨

             □ UI 슬라이더와 오디오 볼륨을 연결할 때 사용

AudioMixer.SetFloat(string, float)

               첫 번째 매개변수: 믹서에서 정의한 파라미터 이름

               두 번째 매개변수: 설정할 값 (일반적으로 dB 단위)

              AudioMixer은 -80부터 +20까지 소리를 지원한다.

              너무 작거나, 너무 크면 소리가 깨지는 현상이 발생하기 때문에 min과 max값 사이에서 볼륨의 크기를 정한다.

 

AudioMixer.SetFloat를 지정하는 일반적인 방법

audioMixer.SetFloat("Master", Mathf.Log10(Mathf.Max(0.001f, volume)) * 20);

: 데시벨(dB)은 로그 스케일이므로 슬라이더(0~1) 변환 시 로그 함수를 권장함

 

 

7. 실행

소리는 안 들린다

  Matser 슬라이더를 조절하면 AudioMixer에 Master 볼륨이 조절된다.

  BGM 슬라이더를 조절하면 AudioMixer에 BGM 볼륨이 조절된다.

  SFX 슬라이더를 조절하면 AudioMixer에 SFX  볼륨이 조절된다.

내일배움캠프-레벨업세션에서 엑셀 파일을 Json으로 변환하는 기능을 봤는데 
이번 프로젝트에서 그대로 사용하기에는 맞지않다고 느껴서
유니티 내부에서 csv파일을 Json으로 저장하는 기능을 만들어보았다. 

 

📝 CsvToJsonConverter.cs

- 전체코드

public class CsvToJsonConverter : MonoBehaviour
{
    /// <summary>
    /// ***Converter 사용 전 주의사항***
    /// 1. 입력한 string, 클래스명, Recources하위의 csv 데이터 이름이 동일해야합니다.
    /// 2. Recources파일 하위에 csv데이터가 존재해야합니다.
    /// 3. csv 데이터를 파싱해서 사용할 클래스 조건
    ///     (1) 클래스여야합니다.
    ///     (2) ICsvParsable 인터페이스를 구현해야합니다.
    ///     (3) 매개변수가 없는 생성자를 가지고 있어야 합니다.
    ///     : ICsvParsable 인터페이스와 사용예시는 Stage 스크립트를 참고해주세요
    /// </summary>

    [Header("===클래스 이름을 작성해주세요===")]
    public string[] className;

    public void CsvConverByName()
    {
        Debug.Log("CsvConverter메서드입니다");

        for (int i = 0; i < className.Length; i++)
        {
            // 여기서 type은 ? 클래스라고 생각하면 편함 
            // string에 맞는 타입 생성 
            Type type = Type.GetType(className[i]);

            if (type == null)
            {
                Debug.LogError($"해당 클래스({className[i]})를 찾을 수 없습니다.");
                continue;
            }

            if (!typeof(ICsvParsable).IsAssignableFrom(type))
            {
                Debug.LogError($"클래스({className[i]})는 ICsvParsable을 구현해야 합니다.");
                continue;
            }

            // CsvDataParsing<> 클래스의 타입을
            // MakeGeneritType : type으로 제네릭 지정
            // convertype : 즉 CsvDataParsing<클래스명>이 된다
            Type converterType = typeof(CsvDataParsing<>).MakeGenericType(type);

            // CsvDataParsing 인스턴스화 
            // 매개변수는 className[i]
            object converterInstance = Activator.CreateInstance(converterType, className[i]);

            // CsvDataParsing<>의 GetDataArray() 메서드 가져오기 
            MethodInfo method = converterType.GetMethod("GetDataArray");

            if (method != null)
            {
                // GetDataArray 메서드 Invoke
                // return 값은 List<T>이지만 object타입으로 박싱(boxing) 일어남 
                // 원본 데이터 배열 가져오기 (List<T> 타입)
                // 컴파일 타임에는 object, 런타임때는 List<T>
                object dataArray = method.Invoke(converterInstance, null);

                // 원본 타입을 유지한 채 JSON으로 변환
                string json = JsonSerialized.ConvertOriginalListToJson(dataArray, type);

                // 결과 저장
                jsonResults[className[i]] = json;

                // 파일로 저장
                JsonSerialized.SaveJsonToFile(json, className[i]);

                Debug.Log($"{className[i]} 데이터를 성공적으로 변환했습니다.");
            }

        }
    }
}

 

 

- 상세코드

Type type = Type.GetType(className[i]);

클래스 이름을 통해서 클래스 타입을 type변수에 저장한다.

Type converterType = typeof(CsvDataParsing<>).MakeGenericType(type);

CsvDataParsing 클래스의 제네릭 타입을 위에서 지정해준 type(현재 클래스의 타입)으로 설정한다. 

object converterInstance = Activator.CreateInstance(converterType, className[i]);

Reflection의 Activator.CreateInstance를 사용해서 제네릭 클래스인 CsvDataParsing를 인스턴스화 한다.

생성자의 매개변수는 현재 클래스이름 

MethodInfo method = converterType.GetMethod("GetDataArray");

  CsvDataParsing 클래스의 GetDataArray()함수를 MethodInfo에 저장한다 

 

if (method != null)
{
    object dataArray = method.Invoke(converterInstance, null);

  GetDataArray메서드를 실행 

  리턴값은 컴파일 타임에는 object 타입으로 저장되고, 런타임때는 List<T> 타입으로 저장된다

 

    // 원본 타입을 유지한 채 JSON으로 변환
    string json = JsonSerialized.ConvertOriginalListToJson(dataArray, type);

    // 파일로 저장
    JsonSerialized.SaveJsonToFile(json, className[i]);

    Debug.Log($"{className[i]} 데이터를 성공적으로 변환했습니다.");
}

■ JsonSerialized 클래스의 ConvertOriginalListToJson() 메서드를 실행 후 json 문자열을 return받는다

JsonSerialized 클래스의 SaveJsonToFile() 메서드를 실행 후 json 파일을 저장한다. 

 

📝 JsonSerialized.cs

- 전체코드

[SerializeField]
public class ListWrapper<T>
{
    public List<T> values;
}
public static class JsonSerialized
{
    // C:\Users\[user name]\AppData\LocalLow\[company name]\[product name]
    static string savePath = Application.persistentDataPath;

    // 원본 타입의 리스트를 변환하는 메서드 (리플렉션 사용)
    public static string ConvertOriginalListToJson(object dataArray, Type elementType)
    {
        // dataArray는 현재 List<T>
        // 1. 적절한 ListWrapper<tyoe> 타입 생성
        Type wrapperType = typeof(ListWrapper<>).MakeGenericType(elementType);

        // 2. 래퍼 인스턴스 생성
        object wrapper = Activator.CreateInstance(wrapperType);

        // 3. values 필드 가져오기
        FieldInfo valuesField = wrapperType.GetField("values");

        // 4. dataArray를 values 필드에 할당
        // dataArray는 object타입이지만 실제로는 List<T> (매개변수로 List<T>를 넘겼기 때문)
        valuesField.SetValue(wrapper, dataArray);

        // 5. JsonUtility로 직렬화
        return JsonUtility.ToJson(wrapper);
    }

    public static void SaveJsonToFile(string json, string saveFileName)
    {
        try
        {
            string path = Path.Combine(savePath, saveFileName);
            File.WriteAllText(path, json);
            Debug.Log($"{saveFileName}이 저장되었습니다. 경로: {path}");
        }
        catch (Exception ex)
        {
            Debug.LogError($"파일 저장 중 오류 발생: {ex.Message}");
        }
    }
}

 

 

- 상세코드

🔖 ConvertOriginalListToJson() 메서드

    // dataArray는 현재 List<T>
    // 1. 적절한 ListWrapper<tyoe> 타입 생성
    Type wrapperType = typeof(ListWrapper<>).MakeGenericType(elementType);

ListWrapper클래스의 제네릭 타입을 위에서 매개변수의 type(현재 클래스의 타입)으로 설정한다. 

 

    // 2. 래퍼 인스턴스 생성
    object wrapper = Activator.CreateInstance(wrapperType);

 Reflection의 Activator.CreateInstance를 사용해서 제네릭 클래스인 ListWrapper를 인스턴스화 한다.

 

    // 3. values 필드 가져오기
    FieldInfo valuesField = wrapperType.GetField("values");

ListWrapper 클래스의 List<T> 타입의 values변수를 FieldInfo 에 저장한다 

 

    // 4. dataArray를 values 필드에 할당
    // dataArray는 object타입이지만 실제로는 List<T> (매개변수로 List<T>를 넘겼기 때문)
    valuesField.SetValue(wrapper, dataArray);

FieldInfo의 SetValue를 통해서 dataArray를 List<T>에 할당한다

 

    // 5. JsonUtility로 직렬화
    return JsonUtility.ToJson(wrapper);

Json으로 직렬화 후 string을 return 한다 

 

🔖 SaveJsonToFile() 메서드

    string path = Path.Combine(savePath, saveFileName);
    File.WriteAllText(path, json);

json 문자열을 지정한 경로에 , 매개변수로 넘어온 파일이름으로 저장한다 

 

📝 ICsvParsable 인터페이스

public interface ICsvParsable
{
    void Parse(string[] values);
}

 

📝 Stage.cs - 테스트용 클래스 

[System.Serializable]
public class Stage : ICsvParsable
{
    [SerializeField] private int hp;
    [SerializeField] private string name;
    [SerializeField] private List<string> animal;

    public void Parse(string[] values)
    {
        hp = int.Parse(values[0]);
        name = values[1];

        animal = new List<string>();
        string[] temp = values[3].Split('-');
        for (int i = 0; i < temp.Length; i++)
        {
            animal.Add(temp[i]);
        }
    }
}

  [System.Serializable]

             □ 직렬화 하기 위해서 클래스 위에 추가한다

csv를 파싱한 데이터를 필드에 넣기위해서 ICsvParsable 인터페이스를 구현한다

Parse(string[] values)

              필드에 맞게 변수를 형변환해서 필드를 설정한다 

 

📝 CsvJsonButton.cs

[CustomEditor(typeof(CsvToJsonConverter))]
public class CsvJsonButton : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        CsvToJsonConverter mana = (CsvToJsonConverter)target;

        if (GUILayout.Button("Cvs to Json"))
        {
            mana.CsvConverByName();
        }
    }
}

에디터에서 실행하기 위해서 커스텀에디터를 만들어준다

 


사전 준비

🔖 csv에서 파싱할 클래스를 작성한다

🔖 해당 클래스는 반드시 CsvParsable인터페이스를 구현해야한다

🔖 csv 파일 이름 , 클래스 이름, 인스펙터 창에 입력한 클래스 이름이 동일해야한다

🔖 Stage클래스 참고 ! 

 

사용방법 ( 테스트환경 )

1. Resources 폴더 내부에 csv 파일 생성

Stage.csv
0.00MB

 

2. Converter 오브젝트의 리스트에 "Stage"입력 후 "Cvs to Json"버튼 클릭 

3. C:\Users\사용자이름\AppData\LocalLow\DefaultCompany\프로젝트이름

의 경로에 Json파일이 생성되었는지 확인한다 

 


📝 깃허브

https://github.com/kimYouChae/UnityCsvToJsonConverter

 

GitHub - kimYouChae/UnityCsvToJsonConverter

Contribute to kimYouChae/UnityCsvToJsonConverter development by creating an account on GitHub.

github.com

 

+ Recent posts