본문 바로가기
Development/Unity BootCamp

[멋쟁이사자처럼 부트캠프 TIL 회고] Unity 게임 개발 3기 18일차

by Mobics 2024. 12. 19.

 

목차


    2D 게임 맛보기

    우선 'Universal 2D'로 새 Project를 만든다.

     

    이후, 받은 Asset 4개를 Import 해준다.

    >> .unitypackage 파일은 그냥 실행하면 설치되며, .psd 파일은 따로 드래그&드롭으로 Import

    2D 캐릭터 Import

    1. 2D Animation 할 캐릭터의 png를 준비

     

    2. 준비한 캐릭터 Slice

    >> 세부 설정 후, Sprite Editor 열기

    ※ 원래 Pivot은 Center였는데, 그렇게 하니 Rigidbody 2D를 적용했을 때 캐릭터가 붕 떠서 수정

     

    3. 빈 게임 오브젝트 만들어서 Sprite Renderer, Animator 추가한 뒤, Slice한 캐릭터 바인딩

     

    4. Animation 추가

    >> Animation 열기 --> 단축키 : Ctrl + 6

    >> Create로 Animation 생성 --> 넣고싶은 만큼 넣으면 그게 Animation 프레임이 된다. (0 ~ 13까지 추가)

    >> Animation이 너무 빠르므로 조정

    : Show Sample Rate를 켜서 1초당 재생할 Animation 프레임 수를 결정 (10으로 설정)

    --> Animator의 'MyCharacter'를 더블클릭해서 Animation State 'MyIdle' 확인

     

    5. Animator 설정

    >> Import한 Animation들을 따로 복붙

    >> Animator Controller 생성

     

    >> Animator에 New Blend Tree 생성

    : Blend Tree의 Motion에 Idle과 Run Animation 넣기--> Parameters의 Blend를 Speed로 이름 변경

    ※ Blend Tree 이름도 Idles로 변경

     

    >> 나머지 Animation들 추가하고 Make Transition으로 화살표 이어주기 --> 'Idles'가 Default State가 아니라면 세팅해주기

     

    >> Character에 Rigidbody 2D와 Capsule Collider 2D 추가

    : Freeze Rotation의 Z를 체크해서 앞으로 고꾸라지지 않도록 설정

    └ Input System을 이용하여 캐릭터 Input 넣기

    >> Package Manager에서 Packages를 Unity Registry로 바꾸고 'Input System'을 검색하여 Install

    ※ Project Settings의 Player에서 Active Input Handling이 'Both'인지 체크 --> Both로 돼있어야 기존의 Input과 추가한 Input System을 같이 사용 가능

     

    >> Input Actions 생성

    ※ W, S, A, D 전부 PC에 체크하기

     

    >> Character에 Player Input 추가 및 설정

     

    >> Character에 Char Controller Script 생성 및 추가

    : 코드 작성

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.InputSystem; // InputAction 추가하면서 생성
    
    public class MyCharController : MonoBehaviour
    {
        private InputAction moveInput;
    
        void Start()
        {
            UnityEngine.InputSystem.PlayerInput input = GetComponent<UnityEngine.InputSystem.PlayerInput>();
            moveInput = input.actions["Move"]; // Input Actions에서 생성한 Actions와 동일한 이름
        }
    
        void Update()
        {
            Vector2 moveValue = moveInput.ReadValue<Vector2>();
            Debug.Log(moveValue);
        }
    }

    Tilemap

    >> Tile Palette 생성

    ※ 이후 저장할 폴더 지정

    ※ 타일들을 담을 폴더를 따로 만들어서 지정

     

    >> Hierarchy에 TileMap 추가 후 Tile 그리기

     

    사용된 타일 확인 방법

     

    >> TileMap 세부 설정

     

    >> 배치한 타일들 떨어지지 않도록 설정 --> 체크 해제

     

    ※ Tile 충돌 설정

    • None : 충돌 X
    • Sprite : Image의 Pixel 단위로 충돌
    • Grid : Grid 단위로 충돌

    >> Tiles

    >> Animated Tile

     

    >> Rule Tile

    : 반복적으로 타일 배치를 해야할 때 유용하게 쓰임 --> 자세한 건 유튜브로 찾자..


    Inventory

    인벤토리 크기를 늘릴 때 Pixel이 안 무너지게 설정하기

    --> 이렇게 설정한 뒤, Width와 Height 값을 늘리면 된다.

     

    >> Inventory 칸 만들기

     

    ※ Button Prefab

     

    >> Inventory.cs : Inventory에 할당

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class Inventory : MonoBehaviour
    {
        [SerializeField]GridLayoutGroup gridLayoutGroup;
        private ItemButton[] buttons;
    
        private int selectedItemIndex1 = -1;
        private int selectedItemIndex2 = -1;
        
        void Awake()
        {
            buttons = gridLayoutGroup.
                GetComponentsInChildren<ItemButton>();
    
            ItemManager itemManager = FindObjectOfType<ItemManager>();
            for (var i = 0; i < buttons.Length; i++)
            {
                var itemData = itemManager.itemDatas[Random.Range(0, itemManager.itemDatas.Count)];
                
                var i1 = i;
                buttons[i].GetComponent<Button>().
                    onClick.AddListener(() => 
                        OnClickItemButton(i1)
                        );
    
                buttons[i].GetComponent<ItemButton>().ItemInfo = new ItemInfo()
                {
                    amount = 1,
                    itemData = itemData
                };
            }
        }
    
        void OnClickItemButton(int index)
        {
            if (0 > selectedItemIndex1)
            {
                selectedItemIndex1 = index;
            }
            else if (0 > selectedItemIndex2)
            {
                selectedItemIndex2 = index;
            
                var itemInfo1 = buttons[selectedItemIndex1].ItemInfo;
                var itemInfo2 = buttons[selectedItemIndex2].ItemInfo;
                buttons[selectedItemIndex1].ItemInfo = itemInfo2;
                buttons[selectedItemIndex2].ItemInfo = itemInfo1;
                selectedItemIndex1 = -1;
                selectedItemIndex2 = -1;
            }
        }
    }

    ※ 'var i1 = i' 로 설정하고 i1을 넣는 이유

    : 이 코드에서는 인벤토리가 실행될 때 for문을 돌면서 i값이 변경된다. 그런데 클로저를 이용하지 않고 i를 직접 참조하면 이미 for을 모두 돌아서 15가 된 i를 계속 참조하게 된다. 하지만 for문 내부에서 지역 변수를 하나 만들어서 클로저를 만들어주면 메모리에 새로운 공간이 할당됩니다. 그래서 같은 이름의 변수명인데도 다른 메모리 공간에 저장돼서 각각 올바른 인덱스 번호를 기억할 수 있게 된다. --> '클로저'

     

    AddListener() 한 다음 함수 집어넣으면, 해당하는 버튼에 OnClick 시 실행하는 함수가 들어가게 된다.

     

     

    >> ItemButton.cs : Button Prefab에 할당

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class ItemButton : MonoBehaviour
    {
        private ItemInfo itemInfo;
        public ItemInfo ItemInfo
        {
            get => itemInfo;
            set
            {
                itemInfo = value;
                SetItemImage(itemInfo.itemData.icon);
            }  
        }
        
        [SerializeField]Image itemImage;
    
        void SetItemImage(Sprite sprite)
        {
            itemImage.sprite = sprite;
            if (sprite == null)
            {
                var color = itemImage.color;
                color.a = 0;
                itemImage.color = color;
            }
            else
            {
                var color = itemImage.color;
                color.a = 1.0f;
                itemImage.color = color;
            }
        }
    }

     

    >> ItemManager.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    [CreateAssetMenu(fileName = "ItemData", menuName = "Datas/ItemData")]
    public class ItemData : ScriptableObject
    {
        public string itemName;
        public Sprite icon;
    }
    
    public class ItemInfo
    {
        public ItemData itemData;
        public int amount;
    }
    
    public class ItemManager : MonoBehaviour
    {
        public List<ItemData> itemDatas = new List<ItemData>();
    }

    : 만든 ScriptableObject 만들기

    2D Game Project Start

    >> CharController.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.InputSystem;
    
    public class CharController : MonoBehaviour
    {
        private static readonly int Speed1 = Animator.StringToHash("Speed");
        [SerializeField] private float Speed = 5.0f;
        [SerializeField] private Camera _mainCamera;
        [SerializeField] private float CameraSpeed = 4.0f;
        [SerializeField] private float MaxDistence = 4.0f;
        
        private Vector3 cameraOffset;
        
        InputAction Move_Input;
        private Animator _animator;
        private Rigidbody2D _rigidbody;
        private SpriteRenderer _spriteRenderer;
        
        void Start()
        {
            _animator = GetComponent<Animator>();
            _rigidbody = GetComponent<Rigidbody2D>();
            _spriteRenderer = GetComponent<SpriteRenderer>();
            
            UnityEngine.InputSystem.PlayerInput Input = GetComponent<UnityEngine.InputSystem.PlayerInput>();
            Move_Input = Input.actions["Move"];
    
            cameraOffset = _mainCamera.transform.position - transform.position;
        }
    
        void Update()
        {
            Vector2 moveValue = Move_Input.ReadValue<Vector2>();
            
            if (moveValue.x != 0)
                _spriteRenderer.flipX = moveValue.x < 0;
            
            _animator.SetFloat(Speed1, Mathf.Abs(moveValue.x));
            transform.position += new Vector3(moveValue.x * Speed, 0, 0)  * Time.deltaTime;
        }
    
        private void LateUpdate()
        {
            var CharPosition = transform.position + cameraOffset;
            float speed = CameraSpeed;
            
            Vector3 newPosition = Vector3.MoveTowards(_mainCamera.transform.position, 
                CharPosition, 
                speed * Time.deltaTime);
    
            _mainCamera.transform.position = newPosition;
        }
    }

    ※ 맵 탐사용 임시 점프 코드

    if (Input.GetKeyDown(KeyCode.Space))
    	_rigidbody.AddForce(Vector2.up * 10f, ForceMode2D.Impulse);

     

    >> 캐릭터의 방향을 전환하는 세 가지 방법

    1. Rotation Y를 -180으로 설정
    2. Scale X를 -1로 설정
    3. Flip을 체크

    ※ 카메라 떨림 해결 방법 유튜브

    https://youtu.be/Zcuu8RBMBFc?si=2qsn20o0LxvY0xXI

     

    >> Quest

    : 카메라 거리 제한 만들어보기

     

    >> Quest2

    : A Pathfinding 공부하기