TIL

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

Cadi 2024. 12. 14. 02:25

 

 

 

 

2D 캐릭터

애니메이션

 

준비된 2D 캐릭터를 보면, 여러개의 모션이 한 장의 사진 안에 있는 PNG 파일이 있다.

이를  sprite Editor 로 slice할 수 있다.

 

 

이렇게 슬라이스하고 생성된 파일들을 배웠던 ( Ctrl + 6 ) 애니메이션 창에 붙여 넣으면

애니메이션이 뚝딱 만들어진다.

 

* 주의 : Sprite Mode : Multiple, Piexels per unit, Filter Mode 등등 조정

* ShowSampleRate 켜서 1초당 애니메이션 수를 조정할 수도 있다.

 

animator controller 생성 후

원하는 애니메이션들을 다 넣고

NewBlendTree 생성 후 더블클릭으로 들어가서

parameter를 Speed로 바꿔주고(나중에 코드로 쓸 것) 기본 상태와 달리는 상태를 세팅해준다.

 

 

 

새로 만든 BlendTree를 기본(idle)로 설정해주고, 기본 상태로 설정해 준 뒤 transit들을 전부 이어준다. 

이런 방식으로 만들면, 저번에 만들었던 Transit를 모두 이어 '조건'을 설정해준 뒤 애니메이션을 만드는 것보다 훨씬

프로그래머 친향적으로 만들 수 있다. 왜 ? 조건들을 일일히 설정하고 ontrigger로 하는 것보다 parameter들의 변화로 

애니메이션을 플레이 딸깍 하는 것이 조금 더 유연하고 편하기 때문에 ! 

 

InputSytem

지난번에는 GetAxis(Horizontal,Vertical)로 Input을 받아왔다면, 요번에는 유니티에서 밀고 있는 기능을 써본다. 

Windows - Package Mamager - Packages - Input System 를 깔아준다. 

 

그리고 Edit - ProjectSettings -Player 에서 다음과 같이 설정해준다. 

 

 

이러면 원래 input System과 새로 생긴 것 둘 다 사용 가능하다. 

 

자, 이제 설정을 했으니 InPutManger를 새로 만들 차례이다. 

프로젝트 창 - creat - input Action(새로 생김)을 누르면 인스펙터 창에 다음과 같이 뜬다.

 

 

 

새로운 컨트롤 계획을 설정해주고 이름과 어떤 장치로 입력을 받을건지를 선택한다. 

예를 들어, 지금과 같이 Pc로 사용할 인풋 시스템이면 Pc로 이름지어주고 키보드를 디바이스타입에 추가한다. 

 

그리고 다음과 같이 action을 이름을 move로 바꿔주고, Value Type의 2D로 바꿔 준 후, action에 키를 할당한다. 

 

이제 캐릭터에 inputsystem이 잘 작동하는지 확인할 순서다. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CharController : MonoBehaviour
{
    InputAction Move_Input;
    
    // Start is called before the first frame update
    void Start()
    {
        UnityEngine.InputSystem.PlayerInput Input = GetComponent<UnityEngine.InputSystem.PlayerInput>();
        Move_Input = Input.actions["Move"];

    }

    // Update is called once per frame
    void Update()
    {
        Vector2 moveValue = Move_Input.ReadValue<Vector2>();
        Debug.Log(moveValue);
        
    }
}

InputSystem에서 playerInput 을 받을 input 변수를 만들고

PlayerInput의 action의 우리가 "move"라고 이름붙인 action을 move_Input에 할당하고 디버그해보면

다음과 같이 나온다. 하나만 눌렀을 때는 1.00, 둘이 같이 눌렀을 때는 0.71로 normalized 된다. 

 

 

 

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.5/manual/Actions.html

 

Actions | Input System | 1.5.1

Actions Related pages: Actions allow you to separate the logical meaning of an input (the things your user can do in your game or app, such as move, jump, crouch) and the device-specific controls (for example, pressing a button or moving a gamepad stick).

docs.unity3d.com

 

왜 Action System을 써야하는지는 따로 글을 쓰던지, 추가하던지 해야겠다. 

간단하게 말하면, 키에 행동을 직접 binding하는 것보다 action 에 먼저 binding 하고 ,action과 행동을 연관짓는 것이

확장성과 유연성 측면에서 더 효율적이기 때문이다. 

 

 

 

 

 

 

 

 

2D 타일 맵 만들기

 

windows - 2D - Tile Palette 를 만들어준다. 

새로운 Tile Palette를 알맞게 이름붙여주고, 

 

원하는 이미지를 다음과 같이 드래그 & 드롭 해준다. 

 

우리는 지금 사각형 Tile을 사용할 것이므로, Hierarchy 창에서 

2D Object - Tilemap - Rectangular 를 만들어준다. 

  • Isometric :크기 [부피,각,용적,길이]가 같은,
    예를 들어 주사위에서 1 2 3 을 갖고 있는 면의 크기가 모두 같게 보이는 시점
  • Rectangle : 기본 정사각형 타일맵
  • Hexagon : 육각형 타일맵 (문명)

             

타일 맵을 만들었으면, 팔레트에서 색칠을 해 줄 수 있다, 팔레트에서 사용할 물감(타일)을 선택하고

타일 맵에 그려준다.  

위의 툴바는 왼쪽부터 차례대로

  • Select the area of the Grid (s)  : 선택 툴
  • MoveSlection with  active Brush(M) : 이동 툴
  • Paint with active Brush(B) : 페인트 브러시 툴
  • Paint a fill box with active Brush(U) : 박스 채우기 툴
  • Pick or marquee select new Brusch(I) : 피커 툴
  • Erase with active Brush(D) :지우개 툴
  • Flood fill with active Brush(G) : 플러드 필 툴 (시작점에서 시작해 인접한 위치 중 조건 만족하면 작업을 이어감)

 

https://docs.unity3d.com/kr/2022.3/Manual/Tilemap-Painting.html

 

타일맵에 페인팅하기 - Unity 매뉴얼

다음은 타일맵에 타일을 페인팅하는 단계와 도구에 대한 설명입니다.그런데 아이소메트릭 타일맵을 페인팅하는 경우에는 아이소메트릭 타일맵을 위한 타일 팔레트 만들기에서 구체적인 내용

docs.unity3d.com

 

오른쪽 위 툴바는 차례대로

  • Toggles Tile Palette Edit : 타일 팔레트 편집 토글
  • Toggle visbility of the Grid in the Tile Palette : 그리드 토글, 그리드 표시 On/Off
  • Toggle visibility of Gizmos in the Tile Palette : Gizmo On/Off, 예를 들어 스프라이트 포함 x > 특별 아이콘 표시
  • Toggle visibility of the Brush Inspector in the Tile Palette : 강조 표시한 토글을 사용하여 인스펙터 확장/축소

https://docs.unity3d.com/kr/2022.3/Manual/Tilemap-Palette.html

 

타일 팔레트 생성 - Unity 매뉴얼

선택한 타일을 타일 팔레트에 놓으면 팔레트에서 타일을 선택하여 타일맵에 페인팅할 수 있습니다. 타일 팔레트를 생성하려면 Window > 2D > Tile Palette로 이동하여 Tile Palette 창을 여십시오. 이 옵션

docs.unity3d.com

 

이렇게 타일 맵에 타일들을 그리고 Collider를 추가하면, 모든 타일이 각각 Collider를 갖고 있는 문제가 발생한다.

이를 최적화 하기 위해 Composite 작업을 해준다. 

 

더해서, 각 Tile은 타일 전체(네모난 크기)로 충돌을 적용할지, Sprite 크기로 적용할지 선택할 수 있다.

  • None : 충돌을 하지 않음
  • Sprite : 이미지 기반 충돌
  • Grid : 격자 기반 충돌

기본 타일 외에도, 알아두면 좋은 두 가지 타일이 있다 ( Project - create - 2D - Tiles)

 

Rule Tile | 2D Tilemap Extras | 4.0.2

Rule Tile Contributions by: johnsoncodehk, DreadBoy, AVChemodanov, DoctorShinobi, n4n0lix This is a generic visual Tile that other Tiles such as the Terrain Tiles, Pipeline Tile, Random Tile or Animated Tiles are based on. There are specific types of Rule

docs.unity3d.com

 

인벤토리 만들기

 

인벤토리는 그 크기가 상황마다 달라지는 경우가 많다. (예를 들어, 인벤 창을 늘이고 줄일때) 

그 때 , 테두리 까지 같이 커지고 작아지는 현상은  부자연스러우므로 이를 해결하기 위해 9슬라이싱을 한다. 

 

 

크기를 변경해 주었을 때, 변화할 부분과 변화하지 않을 부분을 정해주는 것이다.

9슬라이싱을 하고, 캔버스에 게임 오브젝트를 만들고 이미지를 붙여준 뒤, 이미지 타입을 Sliced로 바꿔주면 설정 끝이다.

https://docs.unity3d.com/kr/2021.2/Manual/9SliceSprites.html

 

9슬라이싱 스프라이트 - Unity 매뉴얼

9슬라이싱(9-slicing)은 여러 에셋을 준비할 필요 없이 다양한 크기의 이미지를 재사용할 수 있게 해주는 2D 기술입니다. 이미지를 9개 부분으로 슬라이싱하여 스프라이트의 크기를 재조정할 때 스

docs.unity3d.com

 

 

늘려준 모습

이제, 안에 들어갈 칸들을 만들 것이다. 클릭이 되게 할 것이므로 Button으로 생성해준다. 

text는 필요 없으므로 제거해주고, 이 버튼을 제어할 스크립트(ItemButton)을 만들어 추가해준뒤 프리팹화 한다.

 

이제, 복사해서 채워줄 것인데, 정렬되어서 채워지길 원하므로 게임오브젝트에(Invetory라고 이름붙임) 

GridLayoutGroup 컴포넌트를 추가하고, spacing과 padding을 알맞게 조절해준다. 

그리고 프리팹을 복사해서 계속 생성하면 

다음과 같이 올바르게 정렬되어서 나오는 것을 확인할 수 있다. 

그리고 버튼 프리팹에 이미지를 넣고, 투명도를 줘 보이지 않게 한다. (아이템 이미지가 들어갈 자리 마련)

 

 

이제 코드를 넣어준다. 

Inventory에는 Inventory 코드를

Button 프리팹에는 ItemButton 코드를

새로운 게임오브젝트를 만들어 Manager라고 이름 짓고, ItemManager 코드를 넣어준다. 

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;
    
    // Start is called before the first frame update
    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;
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

 

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;
        }
    }
    
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

 

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>();
    
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

 

아이템 데이터를 생성해보자. Create - Data =Item Data를 생성하고 이미지를 선택해서 넣어준 뒤

ItemManger 인스팩터 창에 넣어준다. 

 

 

 

 

 

ItemButton에 Button을 넣어주는 것도 잊으면 안된다. (이걸로 삼십분 날림)

 

그리고 실행을 누르면

아이템들이 출력되었다, 코드에 따라서 두 개를 누르면 두 개의 위치도 바뀐다. 

 


코드를 분석해가며 왜 이렇게 작동하는지도 알아야 하지만, 일단은 내일 Inventory 코드 분석과

움직이는대로 카메라가 따라가고, 떨어지거나 할 때의 카메라 처리, InputAction을 활용한 코드 분석도 같이 할 것이다.

시간이 남는다면 밑의 키워드들을 따로 공부해야지...

 

알아봐야 할 키워드들

 

FSM(애니메이션에서의)

Input Action

클로저

AddLIstener

유니티 예약어 

Scriptable Object

싱글톤

Abs

LateUptedate