반응형

 

경고 전문
Parent of RectTransform is being set with parent property. Consider using the SetParent method instead, with the worldPositionStays argument set to false. This will retain local orientation and scale rather than world orientation and scale, which can prevent common UI scaling issues.

→ 번역
RectTransform 의 부모는 부모 속성으로 설정됩니다. 대신 setParent 메서드를 사용하고 worldPositionStays 인수를 false로 설정하는 것이 좋습니다. 이렇게 하면 월드 방향과 스케일이 아닌 로컬 방향과 스케일이 유지되어 일반적인 UI 스케일 문제를 방지할 수 있습니다.

 

위의 경고문은  transform.parent 대신 transform.SetParent() 메서드를 사용을 권고하는 경고이다.

RectTransform에서 parent 사용 시 UI에 스케일링 관련 이슈가 있어 SetParent를 사용하는것을 권하고있다.

 

transform.parent = parent_panel; //경고의 원인

transform.SetParent(parent_panel); //이와 같이 SetParent()메서드로 변경해서 사용

 

반응형
반응형

유니티에서 이런 UI를 구현하고자 한다

원리는 간단하다.

탭은 각 UI를 할당하고 있고,

탭버튼을 누를때 다른 종속된 모든UI를 꺼지게하고

내가 누른 버튼에 할당된 UI만 띄우게 하면된다.

결국 각 오브젝트들이 껐다 켜지면서 전환되는 개념이다.
실제 윈도우폼에서의 탭 작동과도 원리가 유사하다

 

 

2개의 cs파일로 간단하게 끝낼 수 있다.

 

기능이 동작하는 방식을 이해하기 쉽도록 아주 간단한 코드로 작성했다

TabButton.cs - 탭 버튼

using UnityEngine.UI;
using UnityEngine;

[RequireComponent(typeof(Button))] //버튼이 없는 오브젝트라면 자동으로 추가
public class TabButton : MonoBehaviour
{
    //해당 버튼 클릭시 열어 줄 UI를 인스펙터창에서 받아온다
    [SerializeField] GameObject panel;
    Image btnImage; //버튼 이미지 
    public GameObject GetPanel => panel;

    TabController parent; //부모 컨트롤러
    private void Start()
    {
        Button btn = GetComponent<Button>();
        btn.onClick.AddListener(SwitchTab); //버튼 리스너에 함수를 할당
        parent = transform.parent.GetComponent<TabController>(); //TabController컴포넌트를 보유한 부모를 가져옴
        btnImage = btn.image; //버튼에 할당된 이미지를 가져옴
    }
    void SwitchTab() //버튼 클릭시 부모에게 내가 눌려졌음을 알림
    {
        parent.SwitchTab(this);
    }
    public void ChangeButtonImage(Sprite _sprite)
    {
        if (btnImage == null) return;
        if (btnImage.sprite != _sprite) //현재 버튼 이미지의 스프라이트와 매개변수 _sprite가 다르다면
            btnImage.sprite = _sprite; //_sprite로 버튼 이미지를 바꿈
    }
}

 

 

TabController.cs - 탭들을 관리하는 관리자 역할

using UnityEngine;

public class TabController : MonoBehaviour
{
    [SerializeField] Sprite btnNormal; //탭버튼이 정상일때
    [SerializeField] Sprite btnSelect; //탭버튼이 눌러진 상태일때

    TabButton[] tabs; //자식들인 탭버튼을 저장할 배열

    // Start is called before the first frame update
    void Start()
    {
        tabs = GetComponentsInChildren<TabButton>();
        SwitchTab(tabs[0]); //첫번째 탭을 눌러준다
    }
    public void SwitchTab(TabButton _target)
    {
        for (int i = 0; i < tabs.Length; i++)
        {
            bool _isActiveTab = _target == tabs[i]; //tabs[i]가 눌려진 버튼인지 판단
            tabs[i].GetPanel.SetActive(_isActiveTab); //모든 탭을 다 꺼준다
            //삼항연산자로 버튼이 눌러졌을때와 일반적인상태일때를 판단해 바꾸어 준다
            tabs[i].ChangeButtonImage(_isActiveTab? btnSelect : btnNormal); 

        }
    }
}

 

두개의 스크립트를 만들고 하이어라키창을 아래와 같이 구성해준다(당연히 입맛대로 설정해주면 된다)

pTabs에는 TabController.cs / 각 탭 버튼에는 TabButton.cs를 넣어주자
아마 TabButton을 넣어주면 버튼이 없을경우 자동으로 생성될 것이다.
해당 버튼이 눌러졌을때 열리게할 패널을 Panel 변수에 넣어주자.

 

모든 탭버튼에 열리게할 패널을 하나씩 각각 넣어주면 버튼쪽은 준비 끝

 

pTabs에는 TabController를 넣어주었다

버튼들의 부모가 되는 pTabs에

BtnNormal, BtnSelect 두개의 스프라이트를 채워넣자.

버튼이 일반적인 상태일때는 BtnNormal의 이미지가 보여질것이고

버튼이 눌려진 상태일때는 BtnSelect 의 이미지가 보여질것이다.

(난 대충 아무거나 넣었다.)

 

pPanels와 그 아래 panel0, panel1 자식들은 UI아무렇게 가져다 꾸몄다.

예시를 위해 바로 보이게끔 0에는 빨강 / 1에는 파랑의 이미지를 넣었다.

대충 이런모양? 아래UI는 원하는대로 필요한 것을 넣으면 된다

 

여러 UI에 탭버튼을 활용해야할때마다 언제든지 갖다 쓰면 될듯하다.

실제로 바로사용해도 무방하나, 프로젝트 상황에 맞게 입맛대로 변경하면 되지싶다.

 

 

퍼가고 맘대로 사용해도 되지만 출처는 꼭 밝히시길..

이해하는데 도움이 되셨다면 아래 하트 부탁함다.

반응형
반응형
유니티 작업 시, 레이아웃을 따라 UI를 잘 꾸며 놓았는데 간혹 특정 UI의 크기를 자동으로 조절을 해야할 때가 있다.
즉, 특정 UI의 사이즈가 자식의 크기에 따라 자동으로 정렬이 되었으면 할때 필요한 기능이다.

딱 이런 경우

이런 경우 자식 오브젝트들의 사이즈에 맞게 가변형으로 바뀌게 할 수 있는 방법이다.

 

1. 사용방법

우선 패널의 인스펙터 내용이다.

간단하게 예시 Image와 HorizontalLayout Group을 추가해줬다

 

아래에서 추가 설명하겠지만,

이 글에서 설명하려는 ContentSizeFitter 컴포넌트의 경우

Layout Group이 필수로 함께 있어야한다

(Vertical  Layout Group 또는 Horizontal Layout Group)

 

나는 Horizontal Layout Group을 추가했다. 

 

하위 자식 오브젝트들은 아무거나 넣어주었고 사이즈도 대충 집어 넣었다.

 

 

 

 

 

 

 

 

 

 

 

이렇게까지만 하면 아마 여전히 아래와 같을 것이다.

 

자 그럼 다시 부모오브젝트인 panel로 돌아가서 Add Componenet ->  Content Size Fitter 추가

그럼 아래 그림과 같은 컴포넌트가 추가된다.

여기서,

가로사이즈를 가변형으로 바꾸고 싶다 하면 Horizontal Fit

세로사이즈바꾸고 싶다하면 Vertical Fit을 조절하면된다.

 

눌러보면 세 개중 하나를 고르도록 되어있다.

Unconstrained의 경우는 원래 그대로의 상태다 (쉽게 말해, 기능 off)

Min Size 혹은 Preferred Size 중 하나를 선택하면 된다.

 

Min Size는 가장 최소화된 사이즈로 맞춰준다.

Preferred Size 적당히 선호되는 사이즈로 맞춰준다.

아래는 Preferred Size를 누른 결과이다

완성

꽤 직관적으로 되어 있어서 몇 번 사용해보면 어떻게 동작하는지 금방 알 수있다.

텍스트의 길이에 따라 사이즈를 조절한다든지도 이걸로 해결할 수 있다.(텍스트는 Preferred Size 만 된다.)

 

 

2. 주의사항 두 가지

1. 경고메시지

다만, 주의 할 점은 이렇게 Layout Group을 사용하고있지 않은 오브젝트는 경고 메시지가 뜨는걸 확인할 수 있다.

ContentSizeFitter는 하위 자식오브젝트들을 감지해야하므로 Layout Group이 필수로 있어야한다

(Vertical  Layout Group 또는 Horizontal Layout Group)

당연히 하위 자식오브젝트들이 어떤 기준으로 정렬이 되어있어야 그에 맞춰 사이즈를 조절할테니..

하지만 내 경우 텍스트의 경우 이를 무시하고 사용할때가 종종있다. 문제없이 됨

 

2. 간혹 안되는 경우

간혹가다 ContentSizeFitter가 특정 상황에서 먹히지 않는 경우가 있다.

해당 부분은 특정상황과 해결방법에 대해 따로 다뤄야할 듯해서 바로 다음 글에 게시하겠다

반응형
반응형

UI Text에 float을 String으로 표현해야 할 때 원하는 소수점 자리까지만 표현을 하고 싶을경우가 있다.

 

단순히 ToString() 함수에 'F' 포맷을 넣어주면 된다.

float a = 1.234f;

a.ToString("F1"); // 1.2

a.ToString("F2"); // 1.23

a.ToString("F3"); // 1.234

 

또 마찬가지로 숫자를 1,000 단위로 콤마를 표현해 String으로 반환하고 싶을때가 있다.

float a = 10000.123f;

a.ToString("N1"); // 10,000.1

a.ToString("N2"); // 10,000.12

a.ToString("N3"); // 10,000.123

F대신에 N으로 바꾸어 주면 된다.(아마도 Numric 의 약자가 아닐까 싶다.)

이렇게 간단한 포맷으로 사용이 가능하다.

반응형
반응형

로컬에 데이터를 저장하는 방법은 여러 가지가 있습니다.

 

저는 저장될 내용에 따라 JSON, 바이너리, PlayerPrefs 등 혼용해서 많이 쓰는데요.

 

그중 이번에는

아마도 유니티 내에서 가장 간단한 PlayerPrefs를 활용해볼까 합니다.

 

 

PlayerPrefs

일단 알아 두셔야 할 점은,

-간편하게 관리할 수 있다는 점.

-간편하고 쉬운만큼 보안성은 개나 줘 버려서 없음. (저장 위치의 파일을 열어보면 날 것 그대로의 값이..)

 

위 같은 이유로 중요한 데이터는 웬만해서는 이 방법을 추천하지 않습니다.

보통, 게임 옵션의 세팅 정보 (BGM, Sound의 볼륨 크기, 진동 유무) 정도로 사용하시는 게 적당하지 않을까 싶네요.

(만약 정말 만약 PlayerPrefs를 활용해서 중요한 게임 데이터를 저장코자 한다면,

꼭 저장 전에 암호화한 값을 저장해주세요. 암호화는 나중에 한 번 다뤄 보도록 하겠습니다.)

 

PlayerPrefs은 기본적으로 Int / Float / String의 변수 타입을 지원합니다.

 

(Bool 변수형의 경우는 지원을 하지 않는 것으로 보이는데, 저의 경우 삼항 연산을 이용 Int로 변환해서 사용합니다.)

 

(배열을 간혹 저장해야 할 때가 있는데 배열을 String으로 변환 후 Spilt을 이용해 사용합니다.)

 

저장하기(Save)

정말 간단히 저장된다.

//기본적으로 PlayerPrefs는 ("키", 값) 형태로 사용

//Int - 정수형 변수의 저장 
PlayerPrefs.SetInt("키", int);

//String - 문자열 형태의 변수 저장
PlayerPrefs.SetString("키", string);


//Float - 실수형의 변수 저장
PlayerPrefs.SetFloat("키", float);

예를 들어

//유저 데이터 세이브

//캐릭터 이름
PlayerPrefs.SetString("CharName", "무예꼬마");

//캐릭터 레벨
PlayerPrefs.SetInt("Level", 10); 

//치명타 확률
PlayerPrefs.SetFloat("Critical", 0.5f);

불러오기(Load)

로드 역시 간단하다.

 

//기본적으로 PlayerPrefs는 ("키", 값) 형태로 사용

//Int - 정수형 변수의 저장 
PlayerPrefs.GetInt("키");

//String - 문자열 형태의 변수 저장
PlayerPrefs.GetString("키");

//Float - 실수형의 변수 저장
PlayerPrefs.GetFloat("키");

 

//유저 데이터 로드

//캐릭터 이름
string charName = PlayerPrefs.SetString("CharName"); //"무예꼬마"

//캐릭터 레벨
int level = PlayerPrefs.SetInt("Level"); //10

//치명타 확률
float criticalPoint =PlayerPrefs.SetFloat("Critical"); //0.5f

끝이다.

 

 

다만 만약에 로드를 했는데 해당 키가 없을 경우 기본 초기값을 설정해주는 방법이 있는데

//불러올때 초기값을 설정해주면, 혹시나 값이 없을때 초기값으로 값을 돌려준다.
PlayerPrefs.GetInt("키", 초기값);

//예를들어
//레벨 데이터를 가져오고, 만약 레벨 데이터가 없다면 1을 불러온다.
PlayerPrefs.GetInt("level", 1);

//String Float 모두 동일하게 사용가능하다.

 

 

아래는 추가적으로 필요한 기능들!

삭제(Delete)

더 이상 필요가 없는 저장된 키값을 지우고 싶을 때가 있다.

//지우고싶은 데이터 삭제
PlayerPrefs.DeleteKey("키");

//저장되어있는 모든 데이터 삭제
PlayerPrefs.DeleteAll();

해당 데이터가 있는지 확인

//해당 이름을 가진 데이터가 있다면 true
if(PlayerPrefs.HasKey("키"))
    Debug.Log("해당 키가 존재합니다.");

 

반응형
반응형

오랜만의 포스팅을 싱글톤으로 하게 되었네요.

 

 

일단 제 기준으로 유니티에서 사용하기 편하고 관리하기 쉬운 방법의 싱글톤 예제를 가지고 왔습니다.

 

싱글톤의 역할

싱글톤은 프로그래밍 디자인 패턴 중 추상 객체 인스턴스 생성 패턴 중의 하나로

유니티에서 싱글톤의 역할은

 

1. 게임 시스템에서 전체를 관장하는 스크립트(단일 시스템 자원 관리 차원)

2. 게임 시스템상 전역 변수의 역할을 하는 스크립트

3. 씬 로드시 데이터가 파괴되지 않고 유지

4. 여러 오브젝트가 접근을 해야 하는 스크립트의 역할

5. 단 한개의 객체만 존재(게임 전체를 관장하는 스크립트가 둘 이상 있으면 꼬이겠죠?)

 

등등 많겠지만 결론은 다 같은 말이죠? 네 이 모든 역할을 할 수 있는게 Singleton Pattern입니다.

 

 

아마 새로운 씬을 로드를 하게 되면 앞에 있던 변수들은 전부 파괴되고 새로운 씬이 로드가 될 거예요.

하지만 싱글톤은 씬 이동시 자신을 파괴하지 않으면서 자신이 가진 데이터들 또한 함께 유지한답니다.

 

실습 예제 및 스크립트

씬이 이동되면서 내 골드의 정보가 초기화 되는 것을 알 수 있습니다.

 

위 그림과 같은 경우로

유니티 내에서 씬과 씬 사이를 넘어갈 때 앞의 씬에서 데이터를 받아와서 다음 씬에서 받고 싶을 때 사용할 수 있어요.

 

그럼 MyGold 가 씬이 이동이 되어도 계속해서 유지할 수 있는 방법을 알아볼게요

 

저는 우선 "GameManager"라는 이름의 스크립트를 만들었습니다.

많은 사람들이 GameManager라는 이름으로 스크립트를 사용 하다보니 유니티에서 아이콘을 바꿔주네요.

 

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

public class GameManager : MonoBehaviour
{
    /* // 싱글톤 //
     * instance라는 변수를 static으로 선언을 하여 다른 오브젝트 안의 스크립트에서도 instance를 불러올 수 있게 합니다 
     */
    public static GameManager instance = null; 

    private void Awake()
    {
        if (instance == null) //instance가 null. 즉, 시스템상에 존재하고 있지 않을때
        {
            instance = this; //내자신을 instance로 넣어줍니다.
            DontDestroyOnLoad(gameObject); //OnLoad(씬이 로드 되었을때) 자신을 파괴하지 않고 유지
        }
        else
        {
            if (instance != this) //instance가 내가 아니라면 이미 instance가 하나 존재하고 있다는 의미
                Destroy(this.gameObject); //둘 이상 존재하면 안되는 객체이니 방금 AWake된 자신을 삭제
        }
    }

    //게임 내에서 씬이동시 유지하고 픈 골드 값(변수)
    public int myGold = 0;
}

 위와 같이 스크립트를 짜주시고, 각 씬의 하이라키창에 빈 오브젝트(Empty Object)를 생성해줍니다.

 

이러면 준비 끝!

이제 어디서든

GameManager.instance.(변수 혹은 함수명)

으로 불러 줄 수 가 있게 되었습니다!

 

지금 의 예제로는

GameManager.instance.myGold 라고 불러 줄 수 가 있겠네요.

 

그럼 Main 씬에서 '돈 벌기' 버튼을 눌렀을때 아래가 작동이 되는 스크립트를 만들어주고,

GameManager.instance.myGold += 2;

씬을 이동(상점) 후, 아이템 구매마다 해당하는 가격만큼 myGold가 줄어들게 설정을 하고 테스트를 해보면..

GameManager.instance.myGold -= (아이템가격);

 

씬 이동을 했음에도 'GameManager'가 파괴되지 않아, 'MyGold' 변수 값이 유지가 되는 모습 
플레이 중에 하이라키에서도 씬 이동이 계속 되어짐에도 GameObject(GameManager Componenet가 들어있는)가 지워지지 않고 유지되고 있다.

주의할 점

쉽게 사용이 가능하다보니 되나 가나 모든 데이터,

객체 등을 이곳에 마구잡이로 때려 넣으면 안 된다는 것!!

 

접근이 쉽다보니 막 갖다쓰다보면 비대칭적인 크기로 게임이 만들어지게되고,

나중에 다시 재정리 하려면 이미 꼬일대로 꼬인 상황이 올거에요.

장담합니다 막 갖다쓰다보면 봅니다.

반응형

+ Recent posts