Unity Editor에서 플레이 중 위와 같은 오류가 뜨면서 Unity가 종료되는 에러가 발생했다.

이 문제로 이틀을 고생했다.

해결방법을 찾아보았다.

 

1. 클린 부팅

https://answers.microsoft.com/ko-kr/windows/forum/all/microsoft-visual-c-runtime-library/b721da15-059d-477b-98eb-6bfacf575191

 

Microsoft Visual C++ Runtime Library 런타임 에러 문의

Microsoft Visual C++ Runtime Library 런타임 에러가 뜨는데 Program: C\Program Files... 라고만 나오니 어디가 문제인지 모르겠고... 같은 문제 겪으셨거나 해결하신 분 방법 아시면 알려주세요... (윈도우10 x64)

answers.microsoft.com

Microsoft에서 공식 답변해준 방법인데 나는 효과가 없었다.

아마도 이건 개발자가 아닌 일반 사용자들이 어떤 프로그램을 사용했을 떄 위와 같은 에러가 발생하면 쓰는 방법인 듯 하다.

 

 

2. Windows 구성 요소 저장소에서 파일 손상 검사

https://barista7.tistory.com/555

 

Microsoft Visual C++ Runtime Library 런타임 에러

Microsoft Visual C++ Runtime Library 런타임 에러가 뜨는데 Program : C\Program... 라고만 나오니 어디가 문제인지 전혀 모르겠다. 분명히 이와 같은 문제 겪었던 사람들이 있을것이란 생각에 검색을 해보았

barista7.tistory.com

위 블로그에 자세히 나와있다.

똑같이 효과는 없었던 걸루,,,

 

 

3. Visual C++ Redistributable Library 재설치

제어판에서 Visual C++ Redistributable 파일들을 모두 삭제하고 최신 버전으로 재설치한다.

설치는 아래 링크에서.

https://support.microsoft.com/ko-kr/topic/%EC%A7%80%EC%9B%90%EB%90%98%EB%8A%94-%EC%B5%9C%EC%8B%A0-visual-c-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-2647da03-1eea-4433-9aff-95f26a218cc0

 

지원되는 최신 Visual C++ 다운로드

시간을 최대한 활용하기 위한 구독

support.microsoft.com

 

블로그를 여러개 찾아봤는데 위 3가지 방법으로 대부분 해결되는 것 같다.

사실 나는 알고보니 호출중인 DLL에서 발생한 오류였음ㅜㅜ

 

스킬 아이콘에 커서를 올리면 설명이 뜨는 UI를 만들었다. (UI가 안예쁜건.. 아직 안꾸몄어요..)

우선 스킬 아이콘 이미지 컴퍼넌트를 가지고 있는 오브젝트에 SkillIcon.cs 스크립트를 추가한다.

 

UnityEngine.EventSystems의 IPointerEnterHadler와 IPointerExitHanler 인터페이스를 사용한다.

OnPointerEnter는 커서가 이미지 위에 올라왔을 때 실행된다 -> 스킬 설명 켜줌

OnPointerExit는 커서가 이미지 밖으로 나갔을 떄 실행된다 -> 스킬 설명 꺼줌

 

혹시 위 코드대로 해도 이벤트가 실행되지 않는다면 SkillIcon 스크립트를 Image 컴퍼넌트가 있는 오브젝트에 붙였는지 확인하세요!!

 

 

 

위에 처럼 텍스트 길이에 대응하여 오브젝트 크기가 조절되게 하기 위해 몇가지 간단한 설정만 남아있다.

 

 

1. 스킬 설명 Text 컴퍼넌트 오브젝트에 Content Size Fitter 컴퍼넌트를 추가한다.

2. 수직에 대응하게 하기 위해 Vertical Fit을 Preferred Size로 한다.

3. 텍스트가 길어질 때 오브젝트가 위로 늘어나게 하기 위해 Pivot을 (0.5, 1)로 설정한다.

아래로 하고 싶다면 (0.5, 0)로 설정한다.

 

그리고 Text의 부모 오브젝트인 뒷배경 이미지도 함꼐 늘어나게 하기 위해서 다음과 같이 설정한다.

 

1. Horizontal Layout Group을 추가한다.

2. Content Size Fitter를 추가하고 Vetrtical Fit을 Preferred Size로 설정한다.

3. Pivot을 (0.5, 0)으로 설정한다.

Horizontal Layout Group의 Padding을 통해서 원하는 만큼 여백을 줄 수 있다.

 

끝!! 간단한다.

오토체스에서 체스 말(이하 유닛)은 별도의 플레이어 조작 없이 자동으로 전투를 한다.

FSM을 사용해서 자동 전투 AI를 구현해보았다.

 

- FSM으로 상태 관리

우선 유닛이 가질 수 있는 상태를 생각해보자.

평상시, 이동, 공격, 사망이 있을 수 있다.

이후 스턴 상태가 추가될 수도 있지만 우선은 이렇게 4가지로 생각했다.

 

유닛의 상태를 나타내는 Enum을 선언해준다.

나는 직관성과 관리의 용이함을 위해 Unit 클래스 내부에 선언해주었다.

 

 

각 상태에는 다음과 같이 발생해야 할 3가지 이벤트가 있다.

1. 상태 진입

2. 상태 유지

3. 상태 탈출

 

각 상태들의 이벤트를 다른 스크립트에서 관리하기 위해 인터페이스를 선언한다.

하나의 스크립트에서 해결할 수 있지만 코드가 길어지기면 복잡해지기 때문에 다른 스크립트에서 관리하기로 했다.

 

 

Enter() : 상태 진입시 한번 실행

Stay() : 상태 유지시 Update문에서 매 프레임 실행

Exit() : 상태 탈출시 한번 실행

 

 

이제 각 상태들의 이벤트를 구현하기 위해서 스크립트를 만들어준다.

 

위에는 죽었을 때 이벤트를 구현할 DeadState.cs이다.

이런 식으로 4가지 상태 모두 스크립트를 만들었다.

 

 

이제 궁극적으로 해당 이벤트를 실행할 Unit 클래스로 돌아가서 나머지 작업을 해준다.

 

 

_ISates라는 Array 변수에 각 상태 클래스를 저장해두었다.

이제 유닛의 상태를 나타내는 변수를 선언해준다.

 

위의 코드를 통해 현재 상태 탈출 이벤트 실행 -> 상태 변화 -> 새로운 상태 진입 이벤트 실행 을 한다.

 

 

Update함수에서 Stay함수를 매 프레임 호출한다.

 

- 가까운 적 찾기

구조

Unit이 Idle 상태이면 가장 가까운 적을 검색

적을 찾으면 적을 향해 이동, Move 상태로 변화

못찾으면 Idle 상태를 유지, 검색 범위를 넓혀가면서 계속 검색

 

IdleStae.cs // Stay함수에서 매프레임 타겟 검색

 

Unit.cs // Update문에서 검색 범위 증가

 

- 적을 향해 이동

구조

Unit이 Walk 상태로 타겟을 향해 이동

타겟이 공격 범위안에 들어오면 이동을 멈추고 공격, Attack 상태로 전환

이동 중이나 공격 중 타겟이 죽으면 Idle 상태로 전환하여 다시 가장 가까운 적 검색

 

MoveState.cs

 

AttackState와 DeadState에도 각각 공격할 떄, 죽었을 떄 일어날 이벤트들을 구현하면 된다.

QuickSheet를 통해 Google 스프레드시트로 Google Spread Sheet로 게임 데이터를 관리하면 편리하다.

QuickSheet는 로컬에 있는 Excel도 지원해주는데 나는 Google 스프레드시트를 사용했다.

다른 플러그인들과 비교해봤을 때 QuickSheet의 가장 좋은 점은 Enum 타입과 Array도 지원해준다는 것!!

 

- 설치 / 연동

다운은 아래 GitHub 링크에서 받을 수 있다.

github.com/kimsama/Unity-QuickSheet

 

kimsama/Unity-QuickSheet

Unity-QuickSheet enables you to use spreadsheet file data within Unity editor. - kimsama/Unity-QuickSheet

github.com

 

연동하는 방법은 다루지 않고 데이터를 관리하는 방법만 다루겠다.

연동하는 방법은 아래 블로그에 자세히 나와있다.

 

lhh3520.tistory.com/345

 

[Unity3D] 유니티에 Google Spread Sheet 연동하는 방법

게임을 만들다 보면 케릭터 레벨 별 경험치 라던지 레벨 별 몬스터 데지미 라던지 등등의 수치를 서버에서 받아와야 하는 경우가 있습니다. 물론 서버가 연동이 된다면 서버에 등록해서 받아오

lhh3520.tistory.com

 

- 구글에서 스프레드시트 만들기

 

구글 드라이브 -> 새로 만들기 -> Google 스프레드시트 클릭

 

상단의 스프레드시트 이름과 하단의 시트 이름을 변경해준다.

시트에서 첫번째 행은 속성(Attirbute)들이 온다.

이때 각 행(레코드)을 구별하기 위해서 고유한 속성을 하나 만든다. (추후에 Unity에서 딕셔너리로 관리하기 위해)

여기선 ID가 그 역할을 한다. 따라서 ID는 중복될 수 없다.

 

스프레드시트 이름 : MyGameData

시트 이름 : TestTable

속성:  ID(int), Name(String), Species(Enum), Item(int Array)

 

- Unity에서 Table 받아오기

이제 Unity에서 테이블을 받아오기만 하면 된다.

Enum 타입의 속성을 사용했기다면 미리 해당 Enum을 선언해야한다.

미리 선언하지 않고 Import를 한다면 당연히 에러가 난다.

 

 

Enum 타입을 사용하지 않았거나 이미 선언해놨다면 위의 과정은 필요 없다.

 

Assets -> Create -> QuickSheet- > Tools- > Google을 클릭해서 새로운 Import Setting을 생성해준다.

 

 

SpreadSheetName과 WorkSheet Name을 넣어준다.

그리고 Import를 누르면 아래와 같이 설정한 속성들이 로드된다.

 

 

Type을 지정해주고 Generate 버튼을 누른다.

아래와 같은 로그가 뜨면 성공적으로 Generate된 것이다.

 

 

 

Assets -> Create -> Google -> TestTable(각자 설정한 시트 이름)을 클릭한다.

Download버튼을 누르면 아래처럼 데이터가 Array로 로드한다.

 

이 파일은 이후 사용되니 위치를 기억해둔다.

 

- Dictionary로 테이블 데이터 관리

이제 각 스크립트에서 데이터를 사용하기 편하게 싱글톤 스크립트에서 데이터를 Dictionary로 저장하여 관리하도록 하겠다.

 

게임의 데이터를 관리해주는 TableData라는 싱글톤 클래스를 만들었다.

그리고 위에서 말한 TestTable 파일을 Inspector를 통해 _testTable이라는 변수에 받아온다.

그리고 이 데이터를 저장할 딕셔너리를 선언한다.

나는 ID의 타입이 int 이기 때문에 딕셔너리 Key의 타입을 int로 하였다.

 

Awake나 Start함수 등 게임이 시작됐을 때 위의 코드를 실행한다.

 

다른 클래스에서 데이터를 받아오기 위한 함수

 

이제 다른 클래스에서 위 코드처럼 간편하게 사용할 수 있다.

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에 원하는 함수를 추가해주면 된다.

+ Recent posts