왜.... 오셨나요?

부끄러워요

유니티개발 TIL

4주차 2일 TIL_유니티 입문(유사 2D메타버스 만들기)

와피했는데 2025. 7. 22. 22:48

들어가기 앞서

유니티 입문단계에 들어서면서 개인 과제로 멀티가 없는 나만의 메타버스(AloneVerse...)를 만드는 프로젝트가 생겼다

우선은 프로젝트 구성과 초기 설계 그리고 케릭터, 맵등 기본적인것을 세팅하고 구현할 예정이다

혼자메타버스 = 혼자버스....


프로젝트 구상 단계


<필요 에셋>

● 2D 맵, 케릭터, 사운드, UI 등

* Github 'README'에 에셋 스토어에서 골라온 무료 에셋들 목록이 있음 참고(https://github.com/Pass1948/Rtanverse)

ZEP


프로젝트 구현 단계


<초기 폴더링>

● 본인만에 폴더규칙에 따라 폴더링

GitIgnore에 외부에셋 추가

와피의 폴더링 규칙
1. 외부 에셋은 모두 'Imorts' 폴더로 저장(패키지에셋은 예외)
2. ResourceManager를 통해 연동 가능한 리소스들은 모두 Resources 폴더에 저장
3. 파일형식대로 폴더링 진행함

 

GitIgnore에 외부에셋 추가
'Imports'라는 폴더를 생성후 에셋스토어 또는 외부 가저온 에셋들은 우선적으로 해당 폴더에 저장한다
GitIgnore에 포함하는 이유는 git에 외부 에셋도 같이 저장될 경우 프로젝트 용량이 급상승하여 이후 
작업환경이 바뀌던지 협업시 어려움이 생길수있음(깊은참조가 안될시도 상정해야함)

<매니저 생성>

GameManager

GameManager의 역할
다른 매니저들의 부모클래스 역할로 싱글톤을 사용

 

=> 코드

더보기
public class GameManager : MonoBehaviour
{
    private static GameManager instance;
    public static GameManager Instance { get { return instance; } }


    // Managers=========================
    private static ResourceManager resourceManager;
    public static ResourceManager Resource { get { return resourceManager; } }


    private void Awake()
    {
        if (instance != null)
        {
            Destroy(this);
            return;
        }

        instance = this;
        DontDestroyOnLoad(this);
        InitManagers();
    }

    private void OnDestroy()
    {
        if (instance == this)
            instance = null;
    }

    private void InitManagers()
    {
        GameObject resourceObj = new GameObject();
        resourceObj.name = "ResourceManager";
        resourceObj.transform.parent = transform;
        resourceManager = resourceObj.AddComponent<ResourceManager>();
    }
}

ResourceManager

 ResourceManager의 역할
Resources 폴더에 있는 리소스 클래스에 불러오기, 삭제 등 리소스 관리 역할

 

=> 코드

더보기
public class ResourceManager : MonoBehaviour
{
    Dictionary<string, Object> resources = new Dictionary<string, Object>();
    public T Load<T>(string path) where T : Object
    {
        string key = $"{typeof(T)}.{path}";

        if (resources.ContainsKey(key))
            return resources[key] as T;

        T resource = Resources.Load<T>(path);

        if(resource == null) Debug.LogWarning($"ResourceManager2D: 리소스 로드 실패 - {key}");

        else resources.Add(key, resource);

        return resource;
    }

    public T Instantiate<T>(T original, Vector2 position, Quaternion rotation, Transform parent) where T : Object
    {
        Vector3 pos3D = new Vector3(position.x, position.y, 0f);                // 2D 게임에서는 Z축을 0으로 설정
        return Object.Instantiate(original, pos3D, rotation, parent);
    }

    //Vector2 위치와 Quaternion 회전만 지정
    public T Instantiate<T>(T original, Vector2 position, Quaternion rotation) where T : Object
    {
        return Instantiate<T>(original, position, rotation, null);
    }


    // 부모 Transform만 지정
    public new T Instantiate<T>(T original, Transform parent) where T : Object
    {
        return Instantiate<T>(original, Vector2.zero, Quaternion.identity, parent);
    }
    // 부모 Transform 없이 기본 위치와 회전으로 생성
    public T Instantiate<T>(T original) where T : Object
    {
        return Instantiate<T>(original, Vector2.zero, Quaternion.identity, null);
    }

    // 경로(path)에 따라 오브젝트를 생성하는 기능
    public T Instantiate<T>(string path, Vector2 position, Quaternion rotation, Transform parent) where T : Object
    {
        T original = Load<T>(path);
        return Instantiate<T>(original, position, rotation, parent);
    }
    // 경로와 Vector2 위치, 회전만 지정
    public T Instantiate<T>(string path, Vector2 position, Quaternion rotation) where T : Object
    {
        return Instantiate<T>(path, position, rotation, null);
    }
    // 경로와 부모 Transform만 지정
    public T Instantiate<T>(string path, Transform parent) where T : Object
    {
        return Instantiate<T>(path, Vector2.zero, Quaternion.identity, parent);
    }
    // 경로만 지정하고 기본 위치와 회전으로 생성
    public T Instantiate<T>(string path) where T : Object
    {
        return Instantiate<T>(path, Vector2.zero, Quaternion.identity, null);
    }

    public void Destroy(GameObject go)
    {
            GameObject.Destroy(go);
    }

    public void Destroy(GameObject go, float delay)
    {
            GameObject.Destroy(go, delay);
    }

    public void Destroy(Component component)
    {
        Component.Destroy(component);
    }

    public void Destroy(Component component, float delay = 0f)
    {
        Component.Destroy(component, delay);
    }

}

<캐릭터 이동>

2D Sprite 랜더링 되는 오브젝트 이기애 리소스적으로 이미지가 다수 존재함

InputSystem 적용한 이벤트 시스템 사용(간편한 키맵핑이 가능한 GUI제공됨)

InputSystem의 InputAction

 

=> 코드

더보기
public class PlayerMove : MonoBehaviour
{
    [SerializeField] float speed;
    [SerializeField] Sprite sRender;	// 옆이동 이미지
    [SerializeField] Sprite bRender;    // 위로 갈때 이미지
    [SerializeField] Sprite fRender;    // 앞으로 이동할때 이미지

    private Rigidbody2D rd;
    private Vector2 moveDir;
    SpriteRenderer spriteRenderer;

    private void Awake()
    {
        rd = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    private void FixedUpdate()
    {
        Move();
    }

    void Move()
    {
        moveDir.Normalize();
        BodyDir();
        rd.velocity = moveDir * speed;		// .velocity : 일정속도로 움직임
    }

    void BodyDir()
    {
        if(moveDir.x > 0)
        {
            spriteRenderer.sprite = sRender;
            spriteRenderer.flipX = true;
        }
        else if(moveDir.x < 0)
        {
            spriteRenderer.sprite = sRender;
            spriteRenderer.flipX = false;
        }

        if(moveDir.y > 0)
        {
            spriteRenderer.sprite = bRender;
        }
        else if(moveDir.y < 0)
        {
            spriteRenderer.sprite = fRender;
        }
    }

    void OnMove(InputValue value)	// inputAction에 있는 키카테고리(On+카테고리이름)으로 정해야 맵핑됨
    {
        moveDir.x = value.Get<Vector2>().x;
        moveDir.y = value.Get<Vector2>().y;
    }
}
Move 함수 로직과 FixedUpdate로 쓴이유
이동시 버튼누르는 입력값을 이벤트로 재동하는 형식의 이동은 update보다 먼저 호출되면서 고정된 시간(0.02초)간격으로 업데이트가 되는  FixedUpdate가 물리로직에 적합함
*특수 상황에서 update메서드에 넣어야 된다면 물리 연사에 Time.fixeddeltatime으로 처리를 해주는 것이 좀더 낮다


<타일맵 구성>

2D Sprite 맵이다 보니 일일히 넣고 맞추는 작업을 편하게 해주는 Tilemap 패키지 사용

Composite Collider 2D 컴포넌트를 사용한 이유
Composite Collider 2D 컴포넌트의 Geometry Type에 Polygons 타입이 오브젝트의 크기에 맞춰서 콜라이더를 생성해주어서 해당 컴포넌트를 적용하게됨

 


버그수정


 

 유니티 프로젝트에 어느순간 무엇을 건들더라도 git 커밋에 변동사항이 나오지 않음
그냥 기존꺼 지우고 새로운 git을 만듬…..


마치며.


깃은 항상 사용하면서 모르겠다 경로가 어떻게 꼬여서 하다가 안되는 현상은 처음 겪어보는것 같다

이게 나혼자 프로젝트 관리하는거라 데미지가 덜하지만 현업가서 그런 버그가 일어나면 큰일날것 같다고 생각이 든다

항상 현상관리유지 신경써야겠다