cos, sin과 반지름값을 이용하여 간단하게 그릴수도 있는데 다른 방법을 이용해봤다.

Mid-Point circle drawing 알고리즘이랑 Bresenham’s circle drawing 알고리즘을 이용하여 원형을 그렸다.

public class CircleDraw : MonoBehaviour
{
    public float fScale = .5f;

    private Vector3 pivot;

    public List<Vector3> vertices;
    public float radius = 5.0f;
    // Start is called before the first frame update
    void Start()
    {
        vertices = new List<Vector3>();
        float heading;
        for (int a = 0; a < 360; a += 360 / 30)
        {
            heading = a * Mathf.Deg2Rad;
            vertices.Add(new Vector3(Mathf.Cos(heading) * radius, Mathf.Sin(heading) * this.radius, transform.position.z));

        }
        for (int i = 0; i < vertices.Count - 1; ++i)
        {
            Debug.DrawLine(vertices[i], vertices[i + 1]);
        }
    }

    // Update is called once per frame
    void Update()
    {
        for (int i = 0; i < vertices.Count - 1; ++i)
        {
            Debug.DrawLine(vertices[i], vertices[i + 1]);
        }
    }
}

 

 

 

'게임 개발 > 유니티' 카테고리의 다른 글

Unity 2D 길찾기 알고리즘 구현  (0) 2020.01.14
Unity로 Sphere 충돌 구현 실험  (0) 2020.01.13
Unity로 CubeCollider 만들기 -제작중-  (0) 2020.01.01
Unity로 Cube 만들기  (0) 2020.01.01
Unity로 Terrain 만들기  (0) 2019.12.30

Unity Cube를 만드는 방법.

 

큐브의 중심은 (0,0,0)의 좌표를 갖는다. 각 Vertice의 배열은 0에서 7까지이다. 1Unit의 큐브를 만들기 위하여

Vertices들에 인덱스 값이 아래와같은 순서로 들어간다.

Vertices 번호

일단 빈 게임오브젝트에 스크립트를 생성 한다.

각 vertice를 생성해준다. 

    private Vector3[] GenerateVertices()
    {

        // 8개의 vertice가 필요
        Vector3[] newVerts = new Vector3[]
        {
            new Vector3(-.5f,-.5f,-.5f), // 0
            new Vector3(-.5f, .5f,-.5f), // 1
            new Vector3( .5f, .5f,-.5f), // 2
            new Vector3( .5f,-.5f,-.5f), // 3
            new Vector3(-.5f,-.5f, .5f), // 4
            new Vector3(-.5f, .5f, .5f), // 5
            new Vector3( .5f, .5f, .5f), // 6
            new Vector3( .5f,-.5f, .5f), // 7
        };
        return newVerts;
    }

각 Vertices의 번호에 따라 Triangles을 만들어준다.

    private int[] GenerateTriangles()
    {
        int[] newTriangles = new int[3*12]
        {
            // left
            4,5,0,
            0,5,1,

            //right
            3,2,7,
            7,2,6,

            // bottom
            4,0,7,
            7,0,3,

            // top
            1,5,2,
            2,5,6,

            // front
            0,1,3,
            3,1,2,

            // back
            5,4,6,
            6,4,7,
        };
        return newTriangles;
    }

 

잘 생성이 되었다.

Wireframe view

 

 

-작성 중-

 

연구 1

Unity로 Terrain 만들기 

 

Mesh에 관한 공부를 하며 직접 터레인을 만들어보았다.

시작 시 터레인이 뜨게 만들었다. 

x축과 z축의 사이즈를 이용하여 원하는 만큼 터레인의 사이즈를 설정한다.

 

Unity는 시계방향(CW)으로 와인딩 순서를 사용한다. 블렌더는 시계 반대 방향으로 와인딩 한다.

따라서 Mesh의 Triangles 순은 0,1,2,  2,1,3으로 넣어줬다.

 

각 Vertice의 위치를 파악하기 위해 Vertices마다 Gizmos의 Sphere로 위치를 파악하게 하였다.

 

연구 2

Vertice의 위치 높낮이를 변경하기.

Material을 설정하고 랜덤 값을 줘서 높이를 변경해봤다.

연구 3

마우스를 클릭하면 클릭한 위치에서부터 지정된 범위까지 높이 변경이 가능하게 만들어보자.

1.  Terrain Collider를 구현한다. 충돌처리를 이용해봐야겠다.

2. 화면을 클릭하면 클릭한 스크린 위치에서부터 터레인까지 레이를 쏘는 부분을 직접 구현한다.

3. 

 

 

 

Script URL : 

 

NGUI 연습 겸 2주 안에 만들어볼 게임을 고르다가 조카의 두뇌에 도움이 될까 싶어 다운로드를 했었던 게임이 떠올랐다. 나름 재밌게 했었고 500만 이상 다운로드된 게임이므로 이 게임을 만들어보기로 했었다.

 

Fill One Line Puzzle게임을 한 사람들은 규칙을 알 것이다. 

각 타일마다 시작점이 있다. 시작점에서부터 상하좌우에 가까이 있는 타일을 터치하면 색이 변해야 한다.

이외의 타일을 터치하면 반응하지 말아야 하며 이전에 터치했던 타일을 터치하면 그 시점부터 다시 시작하게 해야 한다.

 

나는 어떻게 만들지 생각해보다가 생각보다 쉽게 이런 기능을 구현할 수 있는 방법을 찾아냈다.

하나의 Tile에 Box Collider를 아주 작게 만들어 상하좌우로 붙여놓았다. 

이렇게 하면 시작 시 각 타일마다  OnColliderEnter 함수에서 서로 충돌한 Tile들의 정보를 얻게 된다. 

코딩없이 주변에 있는 타일 정보를 가져올 수 있다.

타일을 터치하게 되면 타일이 터치되었다는 flag가 true로 바뀌게 된다. TileControl클래스에 Update() 함수에다가 로드된 스테이지의 모든 타일들을 검사하는 방식으로 확인을 하여 스택에 푸시하는 방법으로 했다.

TileControl class다. Update() 안에 구현 하였다.

지금 보니까 꽤나 무식한 방법으로 한 것 같은데 이 방법 말고 각 Tile이 눌려졌을 때 해당 타일이 눌러졌다는 이벤트를 보내는 방식으로 만드는 게 좋다. (내가 만든 코드를 보니... 리팩토링 각이다.) 눌러야 반응하는 부류의 게임은 웬만하면 Update()에 쓰지 말고 이벤트 처리 방식으로 만들자. 

 

----------

스테이지 추가

 

각 타일들의 정보가 저장되어있는 바이너리 파일이나 텍스트 파일에서 불러오거나 scriptableObject 같은 걸로 타일들을 초기화할 수도 있었지만 색다른 방법으로 스테이지나 타일들 추가가 용이하게 만들어 보려고 했다.

 

LS1_3 (왼쪽 숫자 1은 레벨이고 오른쪽 숫자 3은 스테이지)으로 이름을 바꾸고 타일들을 원하는 대로 배치한다. 

그 후에 처음 시작할 타일을 선택하여 isTouch만 체크해주면 스테이지 생성 완료.

가 아니라... 다 완성한 게 아니어서 완전 자동화로 만들지는 못했다. 때문에 한 가지 더 건드려줘야 할 게 있다. 레벨을 선택하면 스테이지를 선택할 수 있는 화면이 나오는데 스테이지를 복붙 하여 새롭게 만든 스테이지에 MyLevel과 MyStage에 새롭게 만든 스테이지 정보를 적어줘야 한다. 

나름 색다른 방법으로 간단하게 유니티 안에서 스테이지를 추가하는 방법을 적용해봤다.

 

 

프로젝트를 하면서 오브젝트 이름을 이용하여 레벨과 스테이지 저장할 수 있게 만들었다. (안 해도 되는데 문자열 연습 겸 해봤다) 이 정보를 이용하여 게임상에서 스테이지를 선택할 때 해당 스테이지의 정보를 찾아 SetActive(true)로 바꿔준다.

위와 같은 방법으로 레벨마다 스테이지의 개수를 원하는 만큼 추가를 할 수 있게 되었다.

 

-------------

보완해야 될 부분

 

게임 시작 시 각 타일마다 OnCollisionEnter로 주변 타일의 정보를 가져온 후 setActive(false)로 처리하는 방식으로 하였다.

이로 인해 레벨에 있는 모든 스테이지의 타일들의 OnCollisionEnter함수가 실행되어 프레임이 드랍되는 현상이 일어난다. 

메모리에 올라갈 스테이지들

컴퓨터보다 성능이 안 좋은 핸드폰에서 실행을 하게 되면 프레임이 드랍되는 시간이 늘어난다.

1초정도 프레임이 급격히 하락(컴퓨터)

 

또한 모든 스테이지를 메모리에 올리기 때문에 스테이지의 개수가 많아질수록 메모리 사용량도 늘어난다.

 

임시방편으로 Loading화면을 추가하고 코루틴에  WaitForSecondsReatime을 이용하여 모든 타일들의 CollisionEnter함수가 실행될 수 있도록 했다.

성능이 엄청 좋지 않으면 각 Tile들이  충돌된 Tile들의 정보를 Stack에 넣기도 전에 SetActive(false)가 되어 제대로 동작하지 않을 수도 있다. 

 

뗌빵용으로 이런 방법을 썼지만 제대로 된 해결방법은 스테이지들을 프리펩화하여 시작할 때 외부에서 Resources.Load를 이용하여 불러오면 된다.(나중에 리펙토링)

 

 

- 작성 중 -

 

게임을 만들면서 고민을 많이 했던 부분을 여기에 남긴다.

 

이 프로젝트를 하면서 데이터를 저장하기 위해 데이터 설계를 했어야 했고 잊고 있었던 관계형 데이터베이스를 다시 공부하는데 많은 도움이 되었었다.

제작 기간이 한 달 정도밖에 없었지만 게임 제작 전 설계가 중요하다고 생각되어 팀원들을 설득해 설계와 문서작성으로 10일 정도 작성하다 보니 구현할 수 있는 기간이 얼마 남지 않았다는 걸 알게 되었고 문서는 제쳐두고 급하게 프로토 타입 제작을 들어갔다.

 

서버 결정.

집에 그래픽 카드가 망가져서 내장 그래픽카드밖에 사용이 불가능했던 구닥다리 PC 본체가 하나 있었다. 이 PC를 예전에 졸업작품에 사용하기 위해 LAMP서버로 만들어두어 유용하게 사용했었지만 쿨러가 제 역할을 하지 못하여 CPU 온도가 삼겹살을 구워 먹을 수 있을 정도로 올라갔었다. 화제가 날 것 같아 서버를 꺼놓은지 어엿 1년... 학부생 때 아마존 클라우드 수업을 듣다 나도 모르게 큰돈이 빠져나가서 어디서 또 빠져나갈지 몰라 결제 카드정보를 지워놨었다. 요번에 확인해보니 돈을 안내서 계정이 막혀있었다. 그렇다고 또 내고 싶지는 않고... 개인 노트북에 로컬로 웹서버를 만들어서 사용하자니 다른 팀원들에게 업데이트할 때의 번거로움이 싫어서 하지 않았다. 사용하지 않으면 주기적으로 아예 서버가 꺼져버려서 다시 켜야 되는 번거로움이 있지만 구름 IDE를 이용하기로 했다. 기존에 사용했던 PHP가 아닌 Javascript로 만들어보고자 하였다.  

 

------------------------

 

버튼을 눌렀을 때 GameObject를 켜거나 껐을 때 on/off 기능을 직접 이벤트 함수를 만들어서 사용했었다.

게임을 반절 넘게 만들던 도중 문득 GameObject를 열고 닫을 때 단순히 열고 닫기만 하며 이외의 기능은 하지 않는 부분은 유니티에서 제공해주는 GameObject.SetActive를 활용하여 만들었으면 좀 더 빠르게 만들었을 것 같다는 생각을 했다. 알고는 있었지만 왜 이걸 이용하려고 생각을 하지 않았을까... 후우...

다음부터 다른 프로젝트를 할 때는 계속해서 SetActive(true)를 하여 오브젝트를 켰을 때마다 어떤 기능을 해야 하는 함수를 실행시켜야 한다면 아래 사진과 같이 OnClick() 부분을 설정하고 OnEnable()을 이용하거나 이것도 쓰기 싫고 더욱 간편한 방법을 사용하겠다고 하면 SetActive(true)를 했을 때 실행해야 되는 함수를 싹 다 버튼 컴포넌트 OnClick() 부분에 붙여놓기로 하자.

Inspector창에 Button 컴포넌트

이러면 꽤나 구현 시간이 단축된다.

 

----------------

게임이 처음 시작되면 로딩 화면이 나온다. 여기서 NetworkManager라는 싱글톤 클래스를 통해 서버와 연결을 시도한다. 서버와 연결이 되어있는지 체크하기 위해 쿨타임을 만들어 일정 시간마다 서버와 연결을 시도하도록 하였다. 최대 연결 시도 횟수를 설정하여 연결 성공과 실패 부분을 나누었다.

실패할 때에는 팝업을 나타나게 하여 재 연결 시 다시 연결을 시도할 수 있도록 하였다.

 

 

-----------------

-----------------

캐릭터 선택 창에서 UI나 데이터 처리 부분에는 큰 어려움이 없었다.

오른쪽, 왼쪽 버튼을 누르면 캐릭터가 전환이 된다. 이 부분을 구현하기 위해 꽤 많은 고민을 했었다.

캐릭터가 늘어난다고 해도 최대 4개까지만 캐릭터를 추가되므로 캐릭터들을 동서남북으로 세워놨다.

카메라 로테이터 오브젝트를 원형 중앙에 위치시킨 후 메인 카메라를 카메라 로테이터 오브젝트의 자식으로 두었다.

캐릭터 선택 버튼을 누를 때 카메라 원판 중앙에 있는 카메라 로테이터 오브젝트의 각도만 바꾸면 원하던 캐릭터 쪽으로 카메라를 돌릴 수 있었다. 각도를 바꾸면 바로 카메라가 돌아가므로 레이븐처럼 부드럽게 카메라가 움직여야 했다.

부드럽게 구현하기 위해 Mathf.LerpAngle을 이용하였다.

CameraRotator 클래스 구현

 

----------

 

 

NetworkManager를 싱글톤으로 관리하여 캐릭터 장비, 스텟을 웹서버와 주고받게끔 해놨다.

상점에서 11개의 장비를 사는 버튼을 누르고 바로 뒤로 가기로 나가버리면  장비가 서버 DB로 제대로 저장이 되지 않는 문제가 발생했었다.

 

11개 아이템을 구매 후 상점 창을 나가게 되면 ShopPopupPanel이 setActive(false)가 되는데 이때 ShopPopupPanel의 컴포넌트로 붙여져 있는 ShopPopupManager.cs파일에 장비 아이템 팩을 구입하는 코루틴 함수가 실행 중에 setActive(false)가 되어 데이터를 다 주고받지 못하고 끝나버렸기 때문이다.

ShopPopupManager.cs에 PurchaseEquipmenetItemPack() 코드를 보면 NetworkManager에 ISaveEquipmentItemsToServer()의 코루틴 함수를 실행시키는 것을 11번 반복한다. 

ShopPopupManager.cs
NetworkManager.cs

위의 동작을 다 수행하다가 꺼져버리니 통신이 제대로 안되었다. 이를 해결하기 위해 장비 테이블 설계 자체를 변경해볼 생각도 하고 아예 ShopPopupManager를 따로 빼서 별도로 관리하게끔 할까 생각도 해보고 별 생각을 다 해봤는데 크게 고치지 않고도 해결할 수 있는 방법을 찾아냈다.

 

아무것도 터치가 안되게끔 StandByPopupPanel을 하나 만든다. 그 후 구매할 때 StandByPoupPanel을 켜놓고 모든 통신이 성공적으로 끝났을 때 종료되게끔 해놨다. 1개의 장비를 구매할 때는 티도 안 나지만 11개의 장비를 구매할 때 약 0.3초 정도 이 팝업이 뜨다가 꺼져서 살짝 답답한 감이 있을 수도 있지만 크게 코드를 바꾸지 않고 해결을 했다.

 

 

--------------------------

 

 

 

Plane을 하나 만들어서 Position과 Scale 사이즈를 바꿔 왼쪽 하단쪽에 0,0,0으로 좌표를 맞춘다. Transform 값은 아래 사진과 같이 변경하였다.

왼쪽 하단 모서리의 좌표는 (0,0,0)이며 오른쪽 상단 모서리의 좌표는 (400, 0, 400)이 된다.

 

(Plane에 네비게이션을 만들고 구워준다. 플레이어 이동, 네비게이션에 관한 설정은 생략하겠다.)

 

---

 

달빛조각사와 비슷하게 UI를 만들어보았다.

맵 이미지의 왼쪽 하단의 모서리 좌표를 어떻게하면 얻을 수 있을까 이것저것 알아보고 실험하던 차에 방법을 찾았다.
맵 이미지의 앵커를 왼쪽 하단에 맞춘다. 이렇게 해야 이미지의 왼쪽 하단의 모서리 좌표를 구할 수 있기 때문이다.

 

맵 이미지의 좌표값을 얻는 방법은 RectTransform에 offsetMin을 이용하였다. (피봇 위치에 따라 값이 변경됨 피봇을 왼쪽 바닥쪽에 두었기에 offsetMin은 왼쪽 하단 모서리 좌표, offsetMax는 오른쪽 상단 모서리 좌표를 구할 수 있음)

근데 이렇게 하면 화면은 1280 x 720 Landscape로 하고 있는데 스크린 사이즈를 바꾸면 스크린을 터치할 때 픽셀 좌표값과 이미지의 왼쪽 하단의 픽셀 좌표값이 다르게 나온다. (나중에 다른 방법을 찾던가 해야겠다. 아무튼)

 

---

 

버튼 컴포넌트에 OnClick()이벤트 함수를 이용하여 이미지 터치를 처리하였다.

 

 

 

1. 클릭할 때 클릭한 위치의 스크린 좌표를 구해야 한다.

 

UI에서 클릭한 마우스의 스크린 좌표를 구하는 방법은 

- Input.mousePosition으로 좌표값을 얻거나 

- 이벤트로부터 마우스의 좌표를 가져오는 방법이 있다. (이벤트에서 y의 위치는 반전되므로 아래와 같이 연산을 한번 해줘야 한다.)

 

이외에 더 방법들이 있겠지만 나는 Input.mousePosition을 이용하여 좌표값을 가져왔다. (모바일 환경에서 터치를하여 좌표를 구하려면 Input.mousePosition으로 스크린 좌표를 얻을 수 없다. pc에서 실험용으로 하는거라 나는 이걸로 한거다.)

 

 

rectTransform.rect.size를 이용하여 맵 이미지의 사이즈를 구했다.

 

 

 

클릭한 좌표에서 맵 이미지의 왼쪽 하단 모서리 좌표값을 빼면 이미지 사이즈에서 클릭한 좌표값을 구할 수 있다.

이 값을 이미지 크기로 나누면 클릭한 위치의 비율을 알 수 있다.  이 비율을 실제 Plane의 사이즈로 옮기기 위해 Plane의 사이즈를 곱해주면 월드상의 좌표값을 구할 수 있다.

 

구현된 소스코드.

---

 

지도상에서 클릭을 해보니 

빨간색으로 표시한 부분을 클릭해봄.

 

클릭한 위치로 움직인다.

클릭한 위치로 네비메쉬 경로에 따라 플레이어가 움직임

 

---

테스트 영상

https://youtu.be/E-Bnp2C7fEw

 

+ Recent posts