docs.unity3d.com/kr/2018.4/Manual/HOWTO-UIFitContentSize.html

 

UI 요소를 콘텐츠 크기에 맞추기 - Unity 매뉴얼

일반적으로 사각 트랜스폼을 사용하여 UI 요소를 배치하는 경우 포지션 및 크기가 수동으로 지정됩니다(선택적으로 부모 사각 변환을 사용하여 늘릴 동작 포함).

docs.unity3d.com

 

서버는 이전에 게시글로 올린 무료 소켓 서버 Hnetwork를 사용했다.

Hnetwork의 Unity 연동 방법은 Hnetwork 카페에 자세히 나와있다.

bora-game-develop-history.tistory.com/9

 

[Unity/Server] 유니티 간단한 무료 게임 서버 추천

간단한 게임에 서버가 필요한데 돈 주고 사기에는 또 좀 아깝다. 그래서 열심히 구글링해서 찾은 무료 게임 서버 Hnetwork이다. 카페에 들어가면 코드와 사용법이 친절하게 나와있다. 복잡한 처리�

bora-game-develop-history.tistory.com

 

 

데이터베이스는 역시 무료인 MySQL을 사용했다.

www.mysql.com/downloads/

 

MySQL :: MySQL Downloads

Contact MySQL  |  Login  |  Register The world's most popular open source database MySQL.com Downloads Documentation Developer Zone MySQL Database Service is a fully managed database service to deploy cloud-native applications using the world's most po

www.mysql.com

사실 GameSparks라는 게임 백엔드 솔루션을 사용하면 편하지만, GameSparks는 NoSQL인 MongoDB를 사용해서 SQL 공부를 하기 위해 Hnetwork와 MySQL을 사용하기로 결정했다. (Unity: C# / Hnetwork: C++ / MySQL: SQL)

 

 

클라이언트에서 바로 데이터베이스에 접근하여 데이터를 사용할 수 있지만, 이러한 방식은 보안상 좋지 않기 때문에 데이터베이스는 서버에서만 접근할 수 있게 하는 것이 좋다.

 

대표적인 통신 방식

 

Hnetwork 카페에 MySQL 연동 및 데이터 송수신 방법이 나와있다. 해당 게시물을 참고하여서 로그인/회원가입 시스템을 만들었다.

 

UGUI를 이용한 로그인/회원가입 UI

간단하게 InputField와 Button을 이용하여 로그인/회원가입 UI를 만들었다.

MySQL에 User 테이블 생성

데이터베이스의 User 테이블을 생성하여 플레이어의 닉네임과 비밀번호 등의 정보를 저장한다.

테이블 생성은 CREATE TABLE 테이블명 ( 애트리뷰트1, 애트비뷰트2... 애트리뷰트n PRIMARY KEY(애트리뷰트명))

할 수 있다. 아래는 User Table의 모든 레코드를 SELECT한 값이다.

 

 

로그인 통신 절차

1. 로그인 버튼을 누르면 클라이언트는 서버에게 입력한 닉네임, 비밀번호를 포함한 로그인 요청 패킷을 보낸다.

2. 서버는 패킷을 받은 뒤 데이터베이스에 해당 닉네임과 비밀번호를 가진 플레이어가 있는지 확인한다.

3. 서버는 확인 결과(로그인 성공 or 실패)를 클라이언트에게 패킷으로 보낸다.

 

회원가입 통신 절차

1. 회원가입 버튼을 누르면 클라이언트는 서버에게 입력한 닉네임, 비밀번호를 포함한 회원가입 요청 패킷을 보낸다.

2. 서버는 패킷을 받은 뒤 데이터베이스에 해당 닉네임과 동일한 다른 유저가 존재하는지 확인한다.

   여기서 닉네임은 User 테이블에서 기본 키이므로 중복되어서는 안된다.

3. 서버는 확인 결과(닉네임 중복 여부에 따른 회원가입 성공 or 실패)를 클라이언트에게 패킷으로 보낸다. 

 

회원 가입 코드

- 클라이언트

우선 Unity에서 회원가입 버튼을 눌렀을 때 아래 함수를 호출한다. 패킷의 타입을 통해 어떠한 요청을 하는 패킷인지 지정할 수 있다. 아래에서는 회원가입 요청 패킷을 서버로 전송했다.

 

- 서버

서버가 클라이언트에게 패킷을 받으면 OnMessage 함수가 호출되고 받은 패킷의 타입에 따라 다른 이벤트를 실행한다.

패킷이 회원가입 요청 패킷이라면 아래의 Authentication() 함수를 실행한다. 해당 함수에서는 데이터베이스에 접근하여 INSERT INTO user 지시어를 통해 record를 추가한다.

 

 

 

User 테이블에 Newplayer가 추가된 모습

간단한 게임에 서버가 필요한데 돈 주고 사기에는 또 좀 아깝다.

그래서 열심히 구글링해서 찾은 무료 게임 서버 Hnetwork이다.

카페에 들어가면 코드와 사용법이 친절하게 나와있다.

복잡한 처리는 어렵지만 간단한 처리만 필요한 서버에는 괜찮은 듯 하다.

 

cafe.naver.com/makehnetwork

 

고성능 C++ 서버 만들기 : 네이버 카페

고성능 C++ 서버를 지원합니다.

cafe.naver.com

설명서 따라서 연동하면 몇 시간안에 게임 서버 구축 완료!

[SerializeField]
private Text _timerText;
  
private float _timer;
    
    public void StartGame() // 게임 시작 함수
    {
        StartCoroutine(GameScheduler());
    }

    private IEnumerator GameScheduler()
    {
        _timer = 30f;
        isPlaying = true;

        while (_timer > 0 && isPlaying)
        {
            _timer -= Time.deltaTime;
            string minutes = Mathf.Floor(_timer / 60).ToString("00");
            string seconds = (_timer % 60).ToString("00");
            _timerText.text = string.Format("{0}:{1}", minutes, seconds);
            yield return null;

            if (_timer <= 0)
            {
            	// 30초가 다 지나 timer가 0초가 됐을 때 실행할 부분
            }
        }
    }

 

TimeText UI mm:ss 포맷

 

게임내에는 다양한 상수(변하지 않는 수)들이 사용된다.

이러한 상수들은 이후 레벨 디자인을 할 때 쉽고 빠르게 변경될 수 있어야 한다.

그러나 상수들이 각각의 스크립트마다 나눠져 있으면 나중에 찾기도 어려워지고 관리하기 힘들다.

그래서 상수들은 static 클래스를 사용해서 하나의 스크립트에서 관리하면 편한다.

 

위에 처럼 static 클래스를 만들어서 상수를 관리하자.

나중에 가면 복잡한 변수들이 매우 많아지니 주석을 미리미리 달아두는 것을 추천한다.

 

다른 스크립트에서는 위에 처럼 상수를 사용할 수 있다.

System.EventHandler를 이용하여 변수가 변경될 때 정해진 이벤트를 호출하게 설정할 수 있다.

해당 기능은 어떤 변수가 변경되면 연쇄적으로 다양한 이벤트가 발생되어야 할 때 효과적으로 활용할 수 있다.

 

using System;
    
    public event EventHandler OnLevelChanged;
    private int _level;
    public int level
    {
        get => _level;
        set
        {
            _level = value;
            OnLevelChanged?.Invoke(this, EventArgs.Empty);
        }
    }

위 코드를 통해 플레이어의 레벨이 바뀔 때 OnLevelChanged에 등록된 이벤트들을 호출할 수 있다.

 

 

    private void Awake()
    {
        Player.instance.OnLevelChanged += Instance_OnLevelChanged;
    }

    private void Instance_OnLevelChanged(object sender, System.EventArgs e)
    {
    	// 플레이어의 레벨이 바뀔 때 실행하고 싶은 내용
    }

위의 코드는 Player가 Singleton일 때의 경우이다. 이처럼 EventHandler에 원하는 함수를 추가해주면 된다.

public class DontDestroy<T> : MonoBehaviour where T : DontDestroy<T>
{
    private static T instance;
    private void Awake()
    {
        if (instance == null)
            instance = this as T;
        else
            Destroy(gameObject);
        DontDestroyOnLoad(gameObject);
    }
}
    public IEnumerator FadeIn(float time)              
    {
        Color color = image.color;
        while (color.a > 0f)
        {
            color.a -= Time.deltaTime / time;
            image.color = color;
            yield return null;
        }
    }
    
        public IEnumerator FadeOut(float time)
    {
        Color color = image.color;
        while (color.a < 1f)
        {
            color.a += Time.deltaTime / time;
            image.color = color;
            yield return null;
        }
    }
public abstract class GenericSingleTon<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    public static T instance
    {
        get
        {
            if (_instance == null)
            {
                T[] objectList = Resources.FindObjectsOfTypeAll<T>();
                if (objectList.Length > 0)
                    (objectList[0] as GenericSingleTon<T>).Awake();
            }
            return _instance;
        }
    }

    private void Awake()
    {
        if (_instance == null)
            _instance = this as T;
    }
}

게임의 튜토리얼을 제작하다가 다른 input은 무시하고 정해진 버튼만 클릭할 수 있게 해야 하는 상황이 생겼다.

여러가지 방법을 고민해봤다. 나의 경우는 ScrollRect와 Grid Layout Group을 사용해서 버튼 오브젝트의 parent를 바꾸거나 버튼의 sibling을 추가하면 안됐다. 그러다가 내린 결론.

Canvas의 최상단에 주어진 버튼의 rect만 제외하고는 Raycast Target인 투명한 이미지로 가려서 해당 버튼만 눌릴 수 있게..

 

예시 (이후 투명하게 변경)

이렇게 하기 위해서는 총 4개(좌측, 우측, 상단, 하단)의 이미지 오브젝트가 필요하다.

#주의 Canvas의 최상단에 있어야 input을 막을 수 있다.

 

이미지 오브젝트들의 anchor와 pivot은 다음과 같이 설정해준다.

좌측 블럭
우측 블럭
상단 블럭
하단 블럭

 

코드는 다음과 같다.

 

    [SerializeField]
    private RectTransform[] _blocks; // 4개의 이미지 객체들
    
    private enum Block
    {
        Left,
        Right,
        Top,
        Bottom
    }
    
    // targetRectTM : 유저가 터치하길 원하는 오브젝트의 RectTransform
    public void BlockInputExceptRect(RectTransform targetRectTM)
    {
        _blocks[(int)Block.Left].sizeDelta = new Vector2(targetRectTM.position.x - targetRectTM.rect.width / 2, 0);
        _blocks[(int)Block.Right].sizeDelta = new Vector2(Screen.width - targetRectTM.position.x - targetRectTM.rect.width / 2, 0);
        _blocks[(int)Block.Top].sizeDelta = new Vector2(0, Screen.height - targetRectTM.position.y - targetRectTM.rect.height / 2);
        _blocks[(int)Block.Bottom].sizeDelta = new Vector2(0, targetRectTM.position.y - targetRectTM.rect.height / 2);
        for (int i = 0; i < _blocks.Length; i++)
            _blocks[i].gameObject.SetActive(true);
    }

    public void HideBlocks()
    {
        for (int i = 0; i < _blocks.Length; i++)
            _blocks[i].gameObject.SetActive(false);
    }

 

_blocks에는 Inspector에서 좌측, 우측, 상단, 하단 블럭을 순서대로 넣어준다.

#주의 버튼 오브젝트의 Scale이 (1, 1, 1)일 떄만 적용 가능!

 

결과

아주 잘 작동한다!!

+ Recent posts