목차

반응형

1. 개요

2. 중복 로그인 막는 원리

3. 코드

4. 유의사항

 

 

 

 

 

 

 

 

1. 개요

어느 정도 사이즈가 있어서 파이어베이스 로그인을 필요로 하는 게임을 만드는데 2개의 디바이스에서 동일한 소셜 정보로 순차대로 로그인하면 별도의 처리가 없으면 로그인이 잘만 된다.

악용하는 유저도 있고 수많은 버그를 불러일으키는 행위이기 때문에 예방을 필요로 한다.

골치가 아프다. 어떻게 해야지 중복 로그인을 막을 수 있을까?

파이어베이스 실시간 DB의 onvaluechanged 기능을 사옹하면 쉽게 처리 가능하다.

 

 

 

2. 중복 로그인 막는 원리

중복처리를 막기 위한 플로우는 다음과 같다.

 

1. 유저의 소셜 로그인(구글, 애플)

2. 유저 개인 정보를 보관하는 곳에 유저의 device id를 업데이트

3. 유저의 device id에 onvaluechange 트리거를 연결

4. 트리거 작동시 바뀐 값과 현재 유저의 device id가 같은지 확인, 다르면 중복 로그인 팝업과 함께 게임 종료 처리

 

 

3. 코드

1,2 단계는 생략하고 3,4번 코드만 올리도록 하겠다.

 

3. 유저의 device id에 onvaluechange 트리거를 연결하는 코드

DatabaseReference DBRef;

 

//DBRef는 본인 상황에 맞게 적절한 위치를 가져와야 한다.

DBRef = FirebaseDatabase.GetInstance("DB 이름").RootReference;

 

DBRef.Child("Device ID의 위치").ValueChanged += LogoutFunction;

 

4. 트리거 작동 시 바뀐 값과 현재 유저의 device id가 같은지 확인, 다르면 중복 로그인 팝업과 함께 게임 종료 처리

private void LogoutFunction(object sender, ValueChangedEventArgs args)
{
  if (args.DatabaseError != null) 
  {
      Debug.LogError(args.DatabaseError.Message);
      return;
  }

  //device id와 받은 값이 다른 경우
  if (SystemInfo.deviceUniqueIdentifier != (string) args.Snapshot.Value) 
  {
  	//로그아웃 처리
  }
}

 

 

4. 유의사항

당연하지만 유저가 device id 값을 바꾸기 전에 이벤트를 미리 걸어버리면 안 된다.

반응형
반응형

1. 개요

몰랐는데 사실 유니티 IAP(in app purchase) 상품에 대한 국가별 화폐 단위 및 가격을 하드코딩 없이 손쉽게 불러오고 표출하는 방법이 있었다.

따라서 유니티 클라이언트에 상품별 결제 가격에 대한 정보를 전혀 기입할 필요가 없다!

나도 이 기능은 나중에야 알았다. 이런...!

 

2. 원리

1. IAP 상품 정보 등록 및 Initialize

2. product id로 상품 정보 단일 조회

3. 상품 정보에 내재되어있는 로컬 가격과 로컬 화폐단위 획득 및 표출

 

 

3. 코드

1번은 너무 흔하디 흔하니까 스킵하도록 한다. IAP에 대한 코드가 전혀 없다면 아래 페이지를 참고하자

learn.unity.com/tutorial/unity-iap#5c7f8528edbc2a002053b46e

 

Unity IAP - Unity Learn

Unity IAP (In App Purchases) lets you sell a variety of items directly within your free or paid game including premium content, virtual goods and subscriptions. In this tutorial, we are going to look at how to add IAP to an existing game project. This tuto

learn.unity.com

 

 

2. product id로 상품 정보 단일 조회

일반적인 IAP 스크립트라면 라면 IStoreController 타입의 변수 m_StoreController를 가지고 있을 것이다.

해당 변수로부터 product id를 통하여 원하는 product를 리턴해주는 함수를 하나 만들자

public Product GetProduct(string _productId)
{
	return m_StoreController.products.WithID(_productId);
}

 

 

3. 상품 정보에 내재되어있는 로컬 가격과 로컬 화폐단위 획득 및 표출

if (shopItem.ItemType == ItemType.Package)
{
	textPPrice.text = string.Format("{0} {1}", IAP.instance.GetProduct(shopItem.ProductId).metadata.localizedPrice, IAP.instance.GetProduct(shopItem.ProductId).metadata.isoCurrencyCode);
}

위의 코드에 대한 부가설명을 하겠다.

위의 코드는 상품 UI에 부착되어서 textPPrice라는 text 컴포넌트의 텍스트를 지정해주는 부분이다.

상품 UI는 최초에 초기화 단계에서 표출해주는 상품에 대한 product id를 가지고 있어서 위에서 IAP에 만들어뒀던 product 조회 함수를 통해 플레이스토어 및 앱스토어에 등록된 상품에 대한 정보를 가져올 수 있다.

shopItem.ProductId로 조회한 product의 metadata.localizedPrice는 현지 화폐 단위의 가격을 의미하고 metadata.isoCurrencyCode는 화폐 단위를 의미한다.

예를 들어 10000원짜리 상품을 한국 유저의 경우 text에는 10000 KRW라고 표기된다.

 

4. 특이사항

해당 강좌의 전제 조건은 이미 플레이스토어 및 앱스토어에 상품 정보가 등록되어있다고 가정하에 진행했다. 상품 정보를 등록 안 하면 데이터가 안 나오는 게 당연하다.

에디터에서는 전부다 0.01 USD라고 표기된다. 에디터에서만 그렇게 표기되니까 당황하지말고 디바이스에서 테스트해보자

반응형
반응형

목차

1. 원리 파악하기

1.1 검색 조건 정의

1.2 엑셀 데이터를 행 단위로 쪼개기

 

1.3 행 단위로 쪼갠 데이터를 열 단위로 다시 쪼개기

 

2. 데이터 매칭을 위한 키

 

 

 

 

 

1. 원리 파악하기

지난번 강좌에서 TestData로 데이터를 불러오는 부분에 대해서 설명을 하고자 한다.

해당 설명을 들어가기 전에 앞서 스크립터블 오브젝트라는 것에 대한 이해를 하고 있어야 한다.

스크립터블 오브젝트는 추후에 설명하는 글을 써야겠다. 일단은 모른다면 친절한 글이 많으니까 이해하고 오도록 하자

 

TestData라는 오브젝트의 스크립트를 열어보면 크게 두 부분으로 나뉜다.

 

테스트 데이터 객체에 대한 정의

public class TestData : ScriptableObject
{
    public string associatedSheet = "";
    public string associatedWorksheet = "";

    public List<string> items = new List<string>();
    
    public List<string> Names = new List<string>();
    
    //1.3 긁어온 행 데이터를 조회하는 부분
    internal void UpdateStats(List<GSTU_Cell> list, string name)
    {
        items.Clear();
        int math=0, korean=0, english=0;
        for (int i = 0; i < list.Count; i++)
        {
            switch (list[i].columnId)
            {
                case "Math":
                {
                    math = int.Parse(list[i].value);
                    break;
                }
                case "Korean":
                {
                    korean = int.Parse(list[i].value);
                    break;
                }
                case "English":
                {
                    english = int.Parse(list[i].value);
                    break;
                }
            }
        }
        Debug.Log($"{name}의 점수 수학:{math} 국어:{korean} 영어:{english}");
    }

}

 

생성된 테스트 객체의 커스텀 에디터(스크립터블 오브젝트의 인스펙터 부분이라고 생각하면 된다.)

[CustomEditor(typeof(TestData))]
public class DataEditor : Editor
{
    TestData data;

    void OnEnable()
    {
        data = (TestData)target;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        GUILayout.Label("Read Data Examples");

        if (GUILayout.Button("Pull Data Method One"))
        {
            UpdateStats(UpdateMethodOne);
        }
    }

//1.1 GSTU_Search 객체를 생성하는 부분
    void UpdateStats(UnityAction<GstuSpreadSheet> callback, bool mergedCells = false)
    {
        SpreadsheetManager.Read(new GSTU_Search(data.associatedSheet, data.associatedWorksheet), callback, mergedCells);
    }

    void UpdateMethodOne(GstuSpreadSheet ss)
    {
        //1.2 행 데이터를 긁어오기
        foreach (string dataName in data.Names)
            data.UpdateStats(ss.rows[dataName], dataName);
        EditorUtility.SetDirty(target);
    }
    
}

 

 

DataEditor부분에서 생성된 Pull Data Method One 버튼을 클릭하면 TestData의 UpdateMethodOne을 호출한다고 생각하면된다.(정확히는 TestDataEditor의 UpdateStats 함수를 호출하고 결과 콜백을 TestData의 UpdateMethodOne으로 넘겨준다가 맞다. 해당 강좌의 주제는 엑셀이라서 이런 건 몰라도 크게 상관없다.)

 

중점적으로 봐야 하는 부분은 세 곳이다.

 

1.1 검색 조건 정의

void UpdateStats(UnityAction<GstuSpreadSheet> callback, bool mergedCells = false)
{
	SpreadsheetManager.Read(new GSTU_Search(data.associatedSheet, data.associatedWorksheet), callback, mergedCells);
}

여기서 new GSTU_Search로 GSTU_Search 객체를 생성하는 부분이 매우 중요하다.

데이터 검색을 하기 전에 조건을 설정하는 부분이다.

 

GSTU_Search 클래스의 정의를 보자.

public class GSTU_Search
    {
        public readonly string sheetId = "";
        public readonly string worksheetName = "Sheet1";

        public readonly string startCell = "A1";
        public readonly string endCell = "Z100";

        public readonly string titleColumn = "A";
        public readonly int titleRow = 1;

        public GSTU_Search(string sheetId, string worksheetName)
        {
            this.sheetId = sheetId;
            this.worksheetName = worksheetName;
        }

        public GSTU_Search(string sheetId, string worksheetName, string startCell)
        {
            this.sheetId = sheetId;
            this.worksheetName = worksheetName;
            this.startCell = startCell;
        }

        public GSTU_Search(string sheetId, string worksheetName, string startCell, string endCell)
        {
            this.sheetId = sheetId;
            this.worksheetName = worksheetName;
            this.startCell = startCell;
            this.endCell = endCell;
        }

        public GSTU_Search(string sheetId, string worksheetName, string startCell, string endCell, string titleColumn, int titleRow)
        {
            this.sheetId = sheetId;
            this.worksheetName = worksheetName;
            this.startCell = startCell;
            this.endCell = endCell;
            this.titleColumn = titleColumn;
            this.titleRow = titleRow;
        }
    }

 

 

다양한 형태의 GSTU_Search 생성자가 있는데 검색 시작 셀을 지정하고 워크시트의 이름을 정하는 등 다양한 기능을 내포하고 있다.

가장 기본 형태의 GSTU_Search를 생성하였기 때문에 Sheet1 이름을 가진 시트의 A1 셀부터 Z100셀까지 쭈욱 긁어오는 기본적인 검색을 하는 것이다.

일단 검색을 하려면 검색 객체를 만들어야 한다는 것만 알아두고 넘어가자 계속 설명할 것이다.

 

1.2 엑셀 데이터를 행 단위로 쪼개기

데이터를 불러왔으면 데이터의 행단 위로 쪼개서 분석을 해야 한다.

 

void UpdateMethodOne(GstuSpreadSheet ss)
    {
        //data.UpdateStats(ss.rows["Jim"]);
        foreach (string dataName in data.Names)
            data.UpdateStats(ss.rows[dataName], dataName);
        EditorUtility.SetDirty(target);
    }

data.Names에는 Jim, Jay, Jack이라는 행 데이터의 키 역할을 하는 값들이 들어있어서 ss.rows[dataName] 을 통해 행중에서 Jim, Jay, Jack 즉 3 개행의 데이터를 뽑아낸 것이다.

 

1.3 행 단위로 쪼갠 데이터를 열 단위로 다시 쪼개기

뽑아낸 행 데이터를 다시 열 단위로 쪼개서 조회해야 한다.

열 단위 데이터 조회하는 부분은 총 세 가지의 샘플 코드가 있다.

세 가지 형태의 샘플 코드를 한번 봐보자.

//1번 방식
internal void UpdateStats(List<GSTU_Cell> list)
    {
        items.Clear();

        for (int i = 0; i < list.Count; i++)
        {
            switch (list[i].columnId)
            {
                case "Health":
                    {
                        health = int.Parse(list[i].value);
                        break;
                    }
                case "Attack":
                    {
                        attack = int.Parse(list[i].value);
                        break;
                    }
                case "Defence":
                    {
                        defence = int.Parse(list[i].value);
                        break;
                    }
                case "Items":
                    {
                        items.Add(list[i].value.ToString());
                        break;
                    }
            }
        }
    }

//2번 방식
    internal void UpdateStats(GstuSpreadSheet ss)
    {
        items.Clear();
        health = int.Parse(ss[name, "Health"].value);
        attack = int.Parse(ss[name, "Attack"].value);
        defence = int.Parse(ss[name, "Defence"].value);
        items.Add(ss[name, "Items"].value.ToString());
    }

//3번 방식
    internal void UpdateStats(GstuSpreadSheet ss, bool mergedCells)
    {
        items.Clear();
        health = int.Parse(ss[name, "Health"].value);
        attack = int.Parse(ss[name, "Attack"].value);
        defence = int.Parse(ss[name, "Defence"].value);

        //I know that my items column may contain multiple values so we run a for loop to ensure they are all added
        foreach (var value in ss[name, "Items", true])
        {
            items.Add(value.value.ToString());
        }
    }

방법은 다르지만 결국은 똑같은 시트의 Health, Attack, Defence, Items를 긁어오는 것이다.

그냥 이중에 하나만 잘 써도 충분하다. 나는 사실 첫 번째 방법만 계속 써서 다른 두 가지 방법이 있는지 몰랐을 정도로 잘 썼다.

 

internal void UpdateStats(List<GSTU_Cell> list, string name)
    {
        items.Clear();
        int math=0, korean=0, english=0;
        for (int i = 0; i < list.Count; i++)
        {
            switch (list[i].columnId)
            {
                case "Math":
                {
                    math = int.Parse(list[i].value);
                    break;
                }
                case "Korean":
                {
                    korean = int.Parse(list[i].value);
                    break;
                }
                case "English":
                {
                    english = int.Parse(list[i].value);
                    break;
                }
            }
        }
        Debug.Log($"{name}의 점수 수학:{math} 국어:{korean} 영어:{english}");
    }

위의 코드는 첫 번째 방법을 조금 변형한 것이다.

행 단위 데이터를 이루고 있는 열 단위의 데이터 중에서 switch문을 통하여 칼럼 값이 내가 필요한 Math, Korean, English 부분만을 긁어오는 것이다.

 

 

2. 데이터 매칭을 위한 키

이쯤에서 데이터 매칭을 위한 키가 필요하다는 부분을 한번 짚고 넘어가도록 하겠다.

 

당신이 엑셀 아래와 같은 데이터 시트가 있다고 하자.

 

  체력 공격력 방어력
원숭이 99 9 9
군인 99 10 10
고릴라 9999 9999 9999

위 시트의 데이터를 긁어오는 것은 어렵지 않다 충분히 구현할 수 있다.

문제는 캐릭터를 생성할 때마다 시트를 긁어와서 넣어주면 너무 비효율적이기 때문에 한번 긁어와 놓고 어딘가에 저 데이터를 저장해놓고 사용해야 한다는 점이다.

어떻게 할 것인가? 한번 고민해보도록 하자.

우리 강의는 시청자가 고민할 시간을 제공한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

바로 스크롤을 슉슉 내렸는가 사실 나라도 그랬을 것 같다.

여하튼 나의 경우에는 스크립터블 오브젝트를 만들어놓고 해당 스크립터블 오브젝트에 엑셀에서 긁어온 값들을 넣어놓고 사용한다. 그리고 그 스크립터블 오브젝트를 전부 다 들고 있고 관리하는 로컬 데이터베이스 클래스가 있다.

요 구조에 대해서는 나중에 또 실습을 해볼 것이다.

그전에 지금 얘기하고 있는 중요한 부분 데이터 매칭을 위한 키를 정해야 한다는 점에 대해서 얘기하겠다.

 

엑셀에서 데이터를 긁어왔는데 긁어온 데이터는 스스로 목적지를 찾아서 들어가 주지 않는다.

즉 유니티 내부에서 저장해놓을 곳과 엑셀 데이터의 매칭을 위한 약속을 해야 한다는 것이다.

이 부분을 깔끔하게 정하지 않으면 더러운 하드코딩으로 악순환이 돼버릴 수 있다.

 

기본적으로는 절대로 변하지 않는 데이터에 대한 키 값을 정해야 한다.(또한 키가 키 외에 다른 역할을 하지 않도록 하자)

아래 표를 보면 원숭이, 군인, 고릴라는 키 역할을 하면서 동시에 게임 내부에서 오브젝트 명까지 맡고 있다. 충분히 바뀌지 않을 수 있지만 오브젝트 명은 언제든지 바뀔 수 있다. 그렇기 때문에 분리를 해야 한다.

  체력 공격력 방어력
원숭이 99 9 9
군인 99 10 10
고릴라 9999 9999 9999

  이름 체력 공격력 방어력
char_monkey 숭숭이 99 9 9
char_solider 군인 99 10 10
char_gorilla 고륄라 9999 9999 9999
  이름 체력 공격력 방어력
001 숭숭이 99 9 9
002 군인 99 10 10
003 고륄라 9999 9999 9999

이런식으로 분리를 하자는 것이다. 기왕이면 키만 봐도 어떤 데이터인지 알 수 있는 char_monkey 형태가 좋은 것 같다. 나는 001 형식으로 인덱스 값을 부여해서 사용하는데 문제는 딱히 없다.

 

 

유니티 구글 스프레드 시트 연동 - 설정

유니티 구글 스프레드 시트 연동 - 원리 파악

 

반응형
반응형

목차

1. 개요

2. 유니티 플러그인 정보

3. 설정

 

 

1. 개요

유니티에서 구글 스프레드 시트를 연동하는 방법에 대하여 글을 써본다.

 

이미 구글에 관련 게시물들이 올라와있는데 왜 또 쓰는 거냐?(이 게시물은 다른 게시물들과 뭐가 다른 거냐?)

나는 원래 이미 다뤄진 이슈는 잘 안 다루지만 구글 스프레드 시트 연동하는 포스팅을 봤는데 대부분이 정말 초기 세팅 부분만 정리해놓고 그 이후 활용에 대한 내용이 없었다. 그 부분을 널리 알리고자 쓰게 되었다.

 

어떤 경우에 쓰는가?

게임을 혼자서 만들면 굳이 구글 엑셀 시트까지 연동할 필요 없이 넣어주면 된다. 협업 환경에서 기획자가 밸런스나 수치 데이터를 조절할 때마다 내가 할 수는 없으니까, 이를 쉽게 조절할 수 있도록 만드는 경우에 사용한다.

 

어디까지 구글 시트로 연동해야 하는가?

행여나 구글 시트로 연동한다고 했을 때 게임의 수치적인 모든 것을 스프레드 시트로 관리하는 것이 꼭 능사가 아닐 수도 있다.

선별적으로 생각해서 정말로 시트로 관리할 가치가 있는 부분만 시트로 관리하도록 하자. 별로 수정할 일도 없는 부분을 구글 시트로 빼느라 고생하지 말자는 뜻이다.

 

참고사항

참고로 이번 주제에서는 데이터 읽기만 할 것이다. 쓰기는 안한다.

주변의 조언을 종합해서 짧게 말하자면 엑셀 데이터를 긁어오는 것은 개발단계, 관리수준에서 데이터를 쉽게 관리하기 위함이지 게임이 출시되고 나서 인게임에서 엑셀로 데이터를 긁어오는 상황을 위한 것이 아니다.(보안 취약) 따라서 엑셀 시트와 관련된 부분을 전처리하는것이 좋겠다.

 

2. 플러그인 정보

 

https://assetstore.unity.com/packages/tools/utilities/google-sheets-to-unity-73410

 

Google Sheets To Unity | 유틸리티 도구 | Unity Asset Store

Use the Google Sheets To Unity from Greener Games on your next project. Find this utility tool & more on the Unity Asset Store.

assetstore.unity.com

 

유니티에서 해당 플러그인을 설치하자

 

정상적으로 설치가 끝나면 Window -> GSTU 가 생긴다.

 

3. 설정

이 게시물에서는 이 부분이 제일 중요하지만 지루하니까 간단명료하게 설명하겠다.

 

https://console.developers.google.com/

위 페이지 접속, 구글 계정으로 로그인

(사용하고자 하는 구글 스프레드 시트가 로그인된 구글 계정에서 접속 가능해야 한다.)

 

최초 로그인 시 뜨는 부분 : 안 뜨면 넘겨도 됨

최초로 접속한다면 위와 같이 뜬다. 체크하고 동의하자

 

현재 프로젝트가 아예 없어서 새로 생성해야 한다.

 

생성하고 나서 해당 프로젝트로 들어가서 서비스 검색창에 google sheets를 검색해서 들어간 뒤 사용을 눌러서 구글 스프레드 시트를 활성화한다.

 

사용을

 

왼쪽 메뉴 버튼을 눌러서 "API 및 서비스 > 사용자 인증 정보" 로 들어간다.

 

사용자 인증 정보 만들기 > OAuth 클라이언트 ID를 누른다.

 

 

ID를 만들기 전에 프로젝트 정보를 입력해야 한다고 뜬다.

 

외부를 선택하고 애플리케이션 이름을 정한다.

 

위와 같이 선택한다.

 

그러면 클라이언트 ID와 비밀번호가 나온다. 위 데이터를 바로 사용해야 하기 때문에 현재 페이지에서 움직이지 말고 내버려두자

 

유니티로 돌아가서 Window > GSTU > Open Config 로 들어간다.

 

Private 탭에서 위와 같이 복사해서 정보를 기입하고 Port Number는 8080으로 하고 Build Connection을 눌러주자

 

프로젝트를 생성한 구글 계정과 동일한 계정으로 로그인 하자.

 

그러면 위와 같은 창이 뜨는데 "고급 설정 > 이동"을 해준다.

 

권한을 요청하는데 수락해준다.

 

연동되었다고 뜬다. 끝이다. 한 번만 해주면 된다.

 

 

3. 테스트 시트 연동, 데이터 불러오기

그럼 시트를 연동해보도록 하자

 

먼저 프로젝트에서 구글 시트 플러그인을 불러온다.

 

그리고 아래 파일을 다운로드하여서 유니티에서 불러오자.

ReadTest.unitypackage
0.00MB

 

그러면 Google Sheets to Unity > Test > TestData 파일이 있을 것이다.

해당 파일을 클릭하고 Associated Sheet와 Associated Worksheet를 실제 시트에 맞는 데이터로 채워줘야 한다.

 

구글 스프레드시트로 가서 페이지를 하나 만든다.

 

데이터의 형태는 위와 같이 적어놓는다.

 

주소에서 시트의 키와 시트의 이름을 복사하고 아까 유니티의 창에다가 넣고 Pull Data Method One을 눌러준다.

 

위와 같이 데이터가 정상적으로 읽힌다면 성공이다.

엑셀 시트에서 값을 수정하고 읽어오면 바로 바뀐다.

 

다음 포스트는 스프레드 시트를 읽어오는 코드에 대해서 설명하고 복잡한 데이터를 읽어오는 연습을 해보겠다.

 

 

유니티 구글 스프레드 시트 연동 - 설정

유니티 구글 스프레드 시트 연동 - 원리 파악

반응형
반응형

1. 개요

2. 해상도와 비율

3. 대응법

3.1 롤

3.2 벽돌깨기

3.3 철권

3.4 예시 게임

 

 

 

1. 개요

본인은 게임 개발 초기 해상도가 달라지면 어떻게 해야 하는지 복잡해서, 어떻게 해야 하는지 감조차 안 잡혔는데 게임 개발을 시작하는 분들께서 감을 잡을 수 있도록 지금까지 터득한 해상도 대응에 대하여 정리하는 글을 써본다.

먼저 해상도 문제는 왜 발생하는 것인지 그리고 다른 게임들은 어떻게 대처하였는지 짚고 넘어가자.

 

 

2. 해상도와 비율

해상도에 대해서 먼저 탐구를 해보자.

유니티 모바일 기준으로 크게 고려할만한 해상도는 핸드폰, 패드 정도 되겠다.

두 해상도는 다음과 같다.

핸드폰 아이폰 기준 2688 x 1242  비율 2.166

아이패드 2224 x 1668 비율 1.333

 

비율을 보면 핸드폰은 긴 쪽이 짧은 쪽보다 2.166배 길고 아이패드는 1.333배 길다고 보면 된다.

간단하다!

그러면 우리는 대충 2.166, 1.333의 비율의 해상도에서 게임이 정상적으로 작동하게끔 게임을 만들면 나머지 해상도에서도 얼추 돌아가겠거니(시간이 지나면 비율이 3이 되는 디스플레이도 나올지도 모른다.) 생각하면 된다.

 

3. 대응법

해상도에서의 대응법에 대해서 생각해보자.

유니티 카메라로 서로 다른 해상도에 대처를 할 때 두 가지를 고려해야 한다.
1. UI -> 앵커를 이용해서 적당히 꾸겨넣으면 된다.
2. 인게임 요소 -> 게임 플레이가 달라질 수 있기 때문에 생각해봐야 한다.

UI는 앵커를 이용해서 적당히 꾸겨넣으면 된다는 부분은 다른 유니티 강의에서 많이 언급하고 찾아보면 나오기 때문에 넘기도록 하겠다.

유니티 카메라를 정확히 제어하기 위해서는 유니티 유닛(유니티의 카메라로 표기하는 단위)에 대해서도 정확히 알고 있어야 하는데 다음 게시물에서 설명하도록 하겠다.
추후에 해상도별로 몇 유니티 유닛을 나타낼 것인지 계산하는 방법과 실습까지 해보도록 하겠다.

 

먼저 몸풀기로 롤, 벽돌깨기, 철권, 내가 만든 게임 이렇게 4개의 게임을 해상도별로 대응할 때 어떻게 해야 하는지 살펴볼 것이다.(실제 게임과 다를 수도 있음)

 

3.1 롤

만약에 세상의 모든 유저가 1:1 비율의 디스플레이를 가진 컴퓨터로 롤을 한다고 생각하자.

 

1:1 비율의 디스플레이에 롤을 넣어봤다. 모든 사람이 1:1 비율이니까 고려할 사항이 전혀 없다. 모두 행복한 세상이다~

 

그런데... 누군가가 16:9 ( 흔히 알고 있는 1920 x 1080 해상도 ) 비율로 스크린을 만들었다. 그래서 그 해상도에 맞도록 게임을 지원해줘야 한다. -> 하단의 스킬 아이콘은 UI 앵커를 이용해서 적당히 해결된다.

인게임 화면의 경우 롤이나 스타크래프트 같은 게임은 그냥 달라진 비율에 상관없이 보이는 대로 그대로 화면을 보여주면 된다. 문제 될 것이 별로 없다!

 

3.2 벽돌깨기

이번엔 다른 타입의 게임에서 생각해보자.

벽돌깨기는 어떨까?

 

 

1:1 비율의 화면을 기준으로 벽돌깨기 게임을 만들었다.

그리고 모바일 버전으로 만들어야 해서 이 게임을 모바일 화면으로 이식을 한다고 치자. 모바일 화면중 세로가 더 길도록 2:1 비율로 만들었다. 세로 방향으로 기존보다 두배의 공간이 생긴 것이다. 어떻게 처리하겠는가?

 

 

회색 공간은 기존 해상도에 비해서 확장된 영역이다.

당연히 상식이 있다면 위의 화면처럼 처리할 것이다. 비율이 바뀐다고 그에 따라 인게임 화면을 꽉 채워버리면 게임 요소들이 영향받으면 게임이 바뀌기 때문이다. 이렇게 처리하는 것을 레터박스(필러박스)로 채운다고 한다.

이해가 잘 안 가면 나무 위키의 사례를 한번 보자. 아마 아 대충 이런 거 얘기하는 거구나 이해가 갈 것이다.

https://namu.wiki/w/%ED%95%84%EB%9F%AC%EB%B0%95%EC%8A%A4

 

필러박스 - 나무위키

이 저작물은 CC BY-NC-SA 2.0 KR에 따라 이용할 수 있습니다. (단, 라이선스가 명시된 일부 문서 및 삽화 제외) 기여하신 문서의 저작권은 각 기여자에게 있으며, 각 기여자는 기여하신 부분의 저작권을 갖습니다. 나무위키는 백과사전이 아니며 검증되지 않았거나, 편향적이거나, 잘못된 서술이 있을 수 있습니다. 나무위키는 위키위키입니다. 여러분이 직접 문서를 고칠 수 있으며, 다른 사람의 의견을 원할 경우 직접 토론을 발제할 수 있습니다.

namu.wiki

 

여기까지는 이해하기 쉬울 것이다. 하지만 좀 더 복잡한 문제가 발생한다.

 

3.3 철권

이번 예시는 철권이다.

 

철권을 2:1 비율(portrait) 스크린으로 제작했다. (슈퍼 로우 폴리버전이다. 퀄리티는 신경쓰지말자)

 

이 철권을 1:1 비율로 제작해야 하는 상황이 발생하였다. 벽돌깨기와 마찬가지로 레터박스를 사용하면 이렇게 표현해야 한다. 하지만 여기서 한 가지 생각해볼 만한 점이 있다.

 

만약에 철권 캐릭터들이 유저에게는 보이지 않지만 위의 빨간색 박스만큼만 이동할 수 있다면 해상도가 달라져도 빨간색 박스만 화면에 잡히도록 한다면 게임 플레이에는 크게 영향을 안주는 것 아닌가?

그렇다면 어떠한 해상도에서든 빨간색 박스가 잡히도록 카메라를 확대, 축소를 해버리고 지금 필러박스로 가려진 부분을 그냥 공개해버리면 문제없는 것이다.

 

왼쪽처럼 필러박스로 되어 있는 부분을 그냥 화면에 잡히도록 하고 위아래를 꾸며주는 것이다. 하늘과 땅바닥으로 표현했다.

다만 다양한 해상도를 처리하다 보면 빨간색 영역이 너무 작게 잡힐 수도, 너무 크게 잡힐수도 있다. 그래서 유니티에서는 유니티 단위를 계산하여 화면에 최대한 빨간색 영역이 가득 차도록 카메라 확대 배율을 조절할 수 있다.

이게 뭐가 중요하느냐? 싶을 수 있다. 다음 예시를 보자

 

3.4 예시 게임

직접 만들고 있는 게임으로 예시를 들어보겠다.

 

위 사진은 현재 개발 중인 게임을 유니티에서 게임을 자유시점으로 본 것이다. 위의 빨간색으로 쳐져있는 부분이 캐릭터들 이동하는 영역이라서 아까 철권 게임의 빨간 박스가 위의 빨간 박스라고 보면 된다.

 

1. 별다른 처리 없음

1번 예시의 롤같이 해상도에 상관없이 그냥 보여주는 경우다. 게임 플레이가 불가능하다. 조치가 필요하다.

 

2. 필러박스 이용

필러박스를 이용하면 문제는 없지만 게임 몰입을 방해하고 산만해진다.

 

3. 게임을 방해하지 않는 선에서 최대한 확대하는 방식

어떤가 왼쪽은 모바일 해상도, 오른쪽은 패드 해상도다. 화면에 빨간색 영역이 최대한 꽉 차도록 계산하지 않았다면 1번처럼 너무 커서 잘리거나 반대로 너무 작게 보일 수 있다.

 

다음 포스트에서는 유니티 유닛과 카메라에서 어떤 관계를 가지고 있는지 알아보고 해상도별로 대응하는 법을 배워보도록 하겠다.

 

반응형
반응형

1. 롤 공격 애니메이션 속도 분석

2. 원하는 결과물

3. 스크립트 설명

 

 

 

 

1. 롤 공격 애니메이션 속도 분석

게임에서 공격 속도에 따라서 애니메이션을 어떻게 해야 할까?

일단 갓겜 롤을 기준으로 생각해보자.

 

먼저 공격 속도 변수에 대해서 생각해보자. 공격속도는 1회 공격하는 회수에 대한 값이다.

2.5 -> 초당 2.5회 공격

0.5 -> 초당 0.5회 공격

 

두 개의 영상을 준비했다. 크 친절함 보소

 

 

첫 번째 영상은 공격속도 0.2의 그레이브즈

 

두 번째 영상은 공격속도 4.5의 제이스

 

그레이브즈

그레이브즈의 경우 공격 속도가 0.2면 5초에 한 번 공격하는 꼴인데 정상 속도의 공격 애니메이션을 재생하고 약 3~4초간 기다리는 것을 볼 수 있다.

 

제이스

제이스의 경우 공격 속도가 4.5니까 1초에 대략 4대를 때리는데 어찌 보면 좀 어색할 정도로 망치를 엄청 빨리 휘두른다. 즉 애니메이션의 배속이 된 것을 알 수 있다.

 

우리가 구현해야 하는 공격 애니메이션 속도 방식은 다음과 같다.

 

공격 애니메이션을 기본적으로 재생했을 때 걸리는 시간을 1초라고 가정하자.

그렇다면 해당 캐릭터는 공격 속도가 1이면 애니메이션 속도를 변경하지 않고 계속해서 공격하면 문제없다.

만약에 버프나 통하여 공격 속도가 1보다 빨라지면 애니메이션의 재생속도도 빨라진만큼 배속을 해줘야하고 공격속도가 디버프를 통하여 1보다 느려지면 기본 속도로 재생을 시켜주고 남은 시간은 기다리도록 하면 된다.

 

여기서 프레임 개념이 필요하다.

 

이것은 유니티 캐릭터가 공격하는 애니메이션이다.

공격 애니메이션은 기본적으로 1초에 한번 재생되는 것을 기본으로 하였다.

그리고 프레임 재생 비율은 1초에 30 프레임이다.

 

공격 속도 개념과 같이 설명하자면...

공격속도 2 -> 1초에 두번 공격 -> 1초에 60 프레임재생 -> 60/30 비율로 재생 -> 두 배속

공격 속도 1 -> 1초에 한번 공격 -> 1초에 30 프레임재생 -> 30/30 비율로 재생 -> 정배 속

공격 속도 0.5 -> 2초에 한번 공격 -> 2초에 30 프레임재생 -> 30/60 비율로 재생? -> 정배 속으로 재생하고 대기

 

 

2. 원하는 결과물

설명이 좀 길었는데 최대한 친절하게 설명하기 위해서 노력했다.

내가 만들고 싶은 결과물은 다음과 같다.

 

롤과 똑같은 방식이다.

 

3. 유니티 프로젝트 설명

 

공격 애니메이션 에셋은 아래 무료 에셋을 사용했다.

https://assetstore.unity.com/packages/3d/animations/warrior-pack-bundle-1-free-36405

 

Warrior Pack Bundle 1 FREE - Asset Store

This is a sample package of the Warrior Pack Bundle 1 Note: This asset works in Unity 5. This asset contains 3 animations for each of the 4 warriors in the pack (12 animations total), so that you can test and evaluate if they will work for your project bef

assetstore.unity.com

 

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class AttackController : MonoBehaviour
{
    
    /// <summary>
    /// 캐릭터의 애니메이터
    /// </summary>
    [SerializeField] private Animator animator;

    /// <summary>
    /// UI - 공격속도 인풋박스
    /// </summary>
    [SerializeField] private InputField inputFieldAttackSpeed;
    
    /// <summary>
    /// UI - 공격 정보 표기
    /// </summary>
    [SerializeField] private Text textInfo;

    
    // Start is called before the first frame update
    void Start()
    {
        //최초 공격속도 설정
        SetAttackSpeed(attackSpeed);
    }

    /// <summary>
    /// UI로 공격 속도 변경시
    /// </summary>
    public void OnInputFieldChange(InputField inputField)
    {
        SetAttackSpeed(float.Parse(inputField.text));
    }

    /// <summary>
    /// 실제 공격속도 변경해주기
    /// </summary>
    private void SetAttackSpeed(float _attackSpeed)
    {
        attackSpeed = _attackSpeed;
        
        //공격 쿨타임 계산
        attackCoolTime = 1f / attackSpeed;
        
        //최초 공격 바로 시작하도록 설정
        currentAttackCoolTime = attackCoolTime;
        
        //공격속도가 1보다 빠르면 애니메이션 빠르게 재생하기 위해서 배속 설정, 아니면 기본속도 1로 재생
        if (attackSpeed > 1) animator.SetFloat("AttackSpeed", attackSpeed);
        else animator.SetFloat("AttackSpeed", 1);
        
        //UI 초기화
        inputFieldAttackSpeed.text = attackSpeed.ToString();
        textInfo.text = string.Format("공격 속도 : {0}\n" +
                                      "공격 애니메이션 재생하는데 걸리는 시간 : {1}\n" +
                                      "공격 쿨타임 {2}/{3}",attackSpeed,1f/attackSpeed, currentAttackCoolTime,attackCoolTime);
    }
    
    /// <summary>
    /// 공격 시작
    /// </summary>
    public void StartAttack()
    {
        StartCoroutine("EnumAttack");
    }

    /// <summary>
    /// 공격 종료
    /// </summary>
    public void EndAttack()
    {
        StopCoroutine("EnumAttack");
    }

    /// <summary>
    /// 공격속도
    /// </summary>
    private float attackSpeed;

    /// <summary>
    /// 1회 공격하는데 기다려야하는 시간 -> 공격속도 변경시 계산
    /// </summary>
    private float attackCoolTime;

    /// <summary>
    /// 공격하고나서 시간이 얼마나 경과했는지 카운트
    /// </summary>
    private float currentAttackCoolTime;
    
    /// <summary>
    /// 지속적으로 공격
    /// </summary>
    private IEnumerator EnumAttack()
    {
        while (true)
        {
            //만약 공격 쿨타임이 돌았으면
            if (currentAttackCoolTime >= attackCoolTime)
            {
                //쿨타임 초기화하고 공격을 개시한다.
                currentAttackCoolTime = 0;
                Attack();
            }

            textInfo.text = string.Format("공격 속도 : {0}\n" +
                                          "공격 애니메이션 재생하는데 걸리는 시간 : {1}\n" +
                                          "공격 쿨타임 {2}/{3}",attackSpeed,1f/attackSpeed, currentAttackCoolTime.ToString("F2"),attackCoolTime);
            currentAttackCoolTime += Time.deltaTime;       
            yield return null;
        }
    }

    /// <summary>
    /// 공격 트리거 발동
    /// </summary>
    private void Attack()
    {
        animator.SetTrigger("PunchTrigger");
    }
}

 

 

 

이 부분이 핵심이다. 공격 속도가 빠르면 애니메이션의 Speed Multipler 설정에 추가해둔 AttackSpeed라는 Parameter에 따라 빠르게 재생될 수 있도록 하면 된다.

 

해당 스크립트 파일과 전체 프로젝트는 파일로 공유하도록 하겠다.

 

 

프로젝트 전체 다운로드

 

AttackController.cs
0.00MB

 

반응형
반응형

오 드뎌 마지막이다 이번엔 별다른 건 아니고 래그돌이 자꾸 고장 나는 경우가 있는데 그 부분에 대해서 간단하게 기재하고 마무리하고자 한다.

 

https://docs.unity3d.com/kr/530/Manual/RagdollStability.html

 

유니티 - 매뉴얼: Joint And Ragdoll Stability

Joint And Ragdoll Stability Tips for improving joint and ragdoll stability. Avoid small joint angles of “Angular Y Limit” and “Angular Z Limit”. Depending on your setup the minimum angles should be around 5 to 15 degrees in order to be stable. Instead of u

docs.unity3d.com

항목

1. Enable prjection

2. 래그돌 재생성

3. 인위적인 멈춤 사용하지 않기

4. 부위 떨림 발생

 

 

 

1. Enable prjection 활성화

 

구글에서 unity ragdoll strech 라고 검색해보면 대부분 관절에 붙어있는 Enable proejction을 켜주라고 말한다.

 

Enable projection을 하면 무슨 원리로 안정적으로 되는 건지 찾아보았으나 정확한 정보는 얻지 못했고 디컴파일러로 열어보니 해당 불린 변수에 대한 주석으로 위와 같이 나와있었다. 아마 정상적이지 못한 래그돌의 흔들림처리를 이전 상태로 다시 되돌려주는 것이 아닐까 추측해본다.

 

 

2. 래그돌 재생성

나의 경우 래그돌이 두 개의 문제가 있었다. 하나는 래그돌의 양팔이 벌려지고나서부터는 접혀지지가 않는 문제였고 다른 문제는 신체의 일부가 가만히 있고 연결부위가 엿처럼 쭈욱 늘어나는 문제였는데 둘다 래그돌 생성 시 올바른 오브젝트를 매칭 시키지 않아서 발생한 문제였다.

유니티 ragdoll 발사하기 - 2.래그돌 만들기 - 1. 모델 rig 휴머노이드 변경해서 본 정보 알아내기 부분을 참고해서 래그돌을 다시 생성해보도록 하자.

 

 

3. 인위적인 멈춤 사용하지 않기

리지드 바디를 껐다가 켜주면 래그돌이 고장날수도 있다고 적어놨는데 해당 부분을 참고하도록 하자. 고장의 원인이 이것일 수도 있다.

유니티 ragdoll 발사하기 - 6. 래그돌 멈추기 - 글의 2. 두 번째 방법(인위적인 멈춤) 참고

 

 

4. 부위 떨림 발생

래그돌의 일부가 투두ㅜㄱㄷ구국두 하면서 계속 떨린다면

Edit -> Project Settings -> Physics -> “Solver Iteration Count”를 높여보도록 하자

유니티 ragdoll 발사하기 - 2.래그돌 만들기 - 글의 3. 충돌 테스트 참고

 

 

 

 

유니티 ragdoll 발사하기 글 모음

1. 목차

2. 래그돌 만들기

3. 래그돌 발사하기

4. 래그돌이 날아갈 때 한쪽 축으로만 이동되게 하기

5. 현재 래그돌이 멈췄는지 안멈췄는지 판단하기

6. 래그돌 멈추기

7. 래그돌 고장 방지

반응형
반응형

래그돌을 멈추기 위한 두 가지 방법을 찾았다.

첫 번째 방법은 래그돌의 rigidbody를 kinematic으로 바꾸고 현재 가해져 있는 힘을 전부 없애버리는 것이다.

두 번째 방법은 래그돌을 꺼버리는 방법이다.

첫 번째 방법은 자연스러운 멈춤이고 두 번째 방법은 인위적인 멈춤이라고 생각한다.

왜 이렇게 생각이 드는지는 자세한 부분을 듣다 보면 알게 될 것이다.

또한 둘 다 장단점이 있는데 자세한 부분은 설명하면서 기재하도록 하겠다.

 

목차

1. 첫 번째 방법(자연스러운 멈춤)

2. 두 번째 방법(인위적인 멈춤)

3. 각 멈춤에 대한 설명

 

 

 

 

1. 첫 번째 방법(자연스러운 멈춤)

먼저 구현해보고자 하는 멈춤 방식은 다음과 같다.

public Rigidbody body;

public void Freeze()
    {
        body.isKinematic = true;
        body.velocity = Vector3.zero;
        body.angularVelocity = Vector3.zero;
    }
    
public void UnFreeze()
    {
        body.isKinematic = false;
    }

Freeze 함수를 호출하면 동영상처럼 멈추게 되는 것을 알 수 있다.

추가로 알아야 하는 점이라면 물체가 가진 힘을 전부 없애고 싶은 경우(동영상의 세 번째 테스트처럼 래그돌이 날아가다가 모든 힘을 잃고 현재 자리에서 멈추도록 하는 것) Freeze함수를 호출하고 바로 UnFreeze함수를 호출하면 아무 일도 일어나지 않는다.

곰곰이 생각해봤는데 Freeze함수를 호출해서 힘을 전부 다 잃고 나서 프레임이 지나서 해당 물리가 적용되기도 전에 UnFreeze로 다시 원상복구가 돼서 그런 것이 아닐까 생각이 든다.

만약 해당 사항을 스크립트로 적용하고 싶다면 아래처럼 하면 된다.

Freeze();

Invoke("UnFreeze",0.1f);

Freeze에 대한 물리가 적용되고 나서(아마 한 프레임밖에 소요되지 않을 것이라고 생각한다.(UnFreeze에 대한 내용을 FixedUpdate에다가 넣어줘도 되지 않을까 싶다.)

 

 

2. 두 번째 방법(인위적인 멈춤)

위와 같은 멈춤은 매우 간단하다. 위의 멈춤은 뭐랄까 메쉬는 남겨두고 래그돌을 꺼준다고 생각하면 된다.

body.gameObject.SetActive(false);

 

 

 

 

3. 각 멈춤에 대한 설명

각 멈춤은 매우 차이가 크다.

자연 멈춤과 인위 멈춤이라고 명칭을 정하겠다.

자연 멈춤은 멈추고 다시 복원해도 힘을 다 잃어서 연속성이 없다. 그리고 인위 멈춤에 비해서 완전히 얼음!!! 처럼 멈춘다는 느낌은 부족하다. 아마도 모델의 모든 rigidbody를 배열로 받아서 모든 포지션을 저장하고 코루틴으로 해당 rigidbody들의 위치를 계속해서 유지하도록 하면 비슷하게나마 구현할 수 있지 않을까 생각 든다.

인위 멈춤은 멈추고 다시 복원하면 이전의 힘을 그대로 유지한다. 잠깐 래그돌을 껐다가 켜주는 거라서 물리 사항에는 변동이 없다. 굉장히 좋다고 생각할 수 있지만 아래 사진을 보면 인위 멈춤의 부작용이 하나 있다.

아래 사진은 각각의 멈춤을 계속해서 진행하고 나서 래그돌의 상태를 캡처한 것이다.

자연멈춤 / 인위멈춤

몇 번밖에 멈춤을 반복하지 않았다. 자연 멈춤은 아무 문제가 없고 인위 멈춤은 완전히 뒤틀려있음을 볼 수 있다.

인위 멈춤의 경우 충돌로 본이 꼬였을 때 멈추면 해당 상태가 그대로 저장이 된다. 마치 고무가 탄성을 잃는 것이다.

만약에 래그돌을 한두 번만 사용하는 경우라면 인위멈춤을 사용해도 괜찮지만 계속해서 사용해야 하는 경우 비정상적인 결과를 초래하기 때문에 전자의 자연 멈춤을 사용할 수밖에 없을 것이다.

인위 멈춤 방식을 사용해도 계속해서 부위가 꼬이지 않도록 하는 방법을 찾아봤으나 정보가 없었다.

 

 

 

 

유니티 ragdoll 발사하기 글 모음

1. 목차

2. 래그돌 만들기

3. 래그돌 발사하기

4. 래그돌이 날아갈 때 한쪽 축으로만 이동되게 하기

5. 현재 래그돌이 멈췄는지 안멈췄는지 판단하기

6. 래그돌 멈추기

7. 래그돌 고장 방지

반응형
반응형

이 부분은 스크립트만으로 판단한다.

보통이라면 오브젝트의 velocity로 확인을 할 테지만 래그돌이 잠깐 부딪혀서 일시적으로 속도가 0에 가까워지는 경우가 있을 것이고 거의 멈췄으나 경사면이라서 넘어지는 경우도 있을 것이다.

잠깐만 생각해보면 멈췄는지 확인하려면 반드시 일정 시간 동안 움직임이 없는지 확인해야 한다.

 

래그돌에는 여러 신체에 rigidbody가 있는데 팔이나 발 같은 경우 미세한 떨림이 발생할 수 있다.

따라서 몸의 움직임이 가장 적을 것으로 예상되는 pelvis나 spine을 rigidbody로 하는 것이 좋겠다.

 

public Vector3 originPosition;
private int stopCount;

private IEnumerator EnumCheckIsGoatStop()
    {
        while (true)
        {
            if (originPosition!=null)
            {
                //이전 저장위치와의 거리차이가 거의 없는 경우
                if ((spine.transform.position - originPosition).magnitude < 0.01f)
                {
//                    Debug.Log(stopCount);
                    //카운트 증가
                    stopCount++;
                    //카운트가 10번 연속으로 증가하면 멈춤상태
                    if (stopCount > 10)
                        IsGoatStop = true;
                    else
                        IsGoatStop = false;
                }
                else
                {
                    IsGoatStop = false;
                    stopCount = 0;
                }
            }
            originPosition = spine.transform.position;   
            yield return new WaitForSeconds(0.01f);
        }
    }

위의 스크립트는 originPosition에 현재 위치를 저장하고 코루틴이 돌기전에 이전 위치와 비교해서 위치 차이가 0.01f 이하라면 정지라고 1회 카운트를 해준다.

그리고 0.01초 뒤에 다시 코루틴을 시작해서 이번에도 이전 위치와 현재 위치 차이가 0.01 이하라면 정지 카운트에 1을 더해준다.

만약 위치 간의 차이가 0.01보다 크면 정지 카운트를 0으로 초기화하고 정지 판단 불 변수 IsGoatStop을 false로 한다.

 

만약 더 정밀한 정지 판단을 원한다면 if ((spine.transform.position - originPosition).magnitude < 0.01f) 에서 0.01f를 더 낮은 수치로 바꾸면 되겠다.

그리고 정지 판단이 되기까지 가장 빠르면 0.01초 x 10 => 0.1초의 시간이 소요되는데 더 빠르게 판단을 하고 싶다면 

yield return new WaitForSeconds(0.01f);의 0.01f의 값을 더 낮은 수치로 바꾸면 되겠다.

 

rigidbody 오브젝트가 생성되고 나서 StartCoroutine("EnumCheckIsGoatStop");를 통하여 코루틴을 실행하면 되겠다.

 

 

 

유니티 ragdoll 발사하기 글 모음

1. 목차

2. 래그돌 만들기

3. 래그돌 발사하기

4. 래그돌이 날아갈 때 한쪽 축으로만 이동되게 하기

5. 현재 래그돌이 멈췄는지 안멈췄는지 판단하기

6. 래그돌 멈추기

7. 래그돌 고장 방지

반응형
반응형

래그돌을 날리는 것까지는 됐는데 2D 환경에서는 한쪽 축 없이 잘 이동하겠지만 3D 환경에서는 한쪽 축으로만 이동시키려면 다른 방법이 필요하다.

일단 rigidbody의 freeze position을 사용하면 어색하기 때문에 다음과 같은 방법을 사용함을 밝힌다.

이번 내용 역시 짧아서 목차없이 진행한다.

 

엄청 커서 캐릭터가 넘어가지 못하도록 벽의 역할을 할 큐브 2개를 소환한다.

위와 같이 양옆을 막아줘야한다.

 

벽의 Mesh Renderer를 삭제해서 안 보이도록 해준다.

 

public class testGoat : MonoBehaviour
{
    public Rigidbody body;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            Vector3 diffVector = new Vector3(1, 1, 0);
            body.AddForce(diffVector * 1500);
        }
    }
    
    public GameObject Wall1;
    public GameObject Wall2;

    private void FixedUpdate()
    {
        Vector3 tempPos = body.transform.position;
        
        tempPos.z = Wall1.transform.position.z;
        Wall1.transform.position = tempPos;
        
        tempPos.z = Wall2.transform.position.z;
        Wall2.transform.position = tempPos;
    }
}

기존의 캐릭터 스크립트에 위와 같이 FixedUpdate와 Wall1, Wall2 변수를 작성한다.

 

익스펙터에서 넣어준다.

 

그러면 캐릭터가 어떻게 움직여도 한쪽 방향으로만 움직이게 된다.

 

 

 

유니티 ragdoll 발사하기 글 모음

1. 목차

2. 래그돌 만들기

3. 래그돌 발사하기

4. 래그돌이 날아갈 때 한쪽 축으로만 이동되게 하기

5. 현재 래그돌이 멈췄는지 안멈췄는지 판단하기

6. 래그돌 멈추기

7. 래그돌 고장 방지

반응형