1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public class ScrollStage : MonoBehaviour {
 
    /*
     * 스테이지 순환 및 각 스테이지 패널에 들어갈 오브젝트 랜덤 배치에 관한 스크립트 입니다.
     * This scripts responsible for stage rotation and random placement objects into each stage panel
     */
 
    public GameObject[] stages;
    public GameObject fuel;
    public GameObject block;
    public GameObject boost;
    public GameObject trap;
 
    void Start () {
        InitStagesPosition();
    }
    
    void Update () {
        StageMoves();
    }
 
    //첫번째 스테이지 오브젝트 기준으로 초기 위치를 설정합니다.
    //Set the initial position based on the first stage object
    private void InitStagesPosition()
    {
        stages[0].transform.position = Vector2.zero;
 
        for(int i = 1; i < stages.Length; i++)
        {
            //바로 전 스테이지의 바닥 collider사이즈 뒤에 배치합니다. 
            float _xOffset = stages[i - 1].transform.GetChild(0).GetComponent<BoxCollider2D>().size.x;
            float _initXPos = stages[i - 1].transform.position.x + _xOffset;
 
            stages[i].transform.position = new Vector2(_initXPos, 0f);
 
            //초기 스테이지 오브젝트 랜덤 배치
            RePositionObjcet(fuel, stages[i], StatusManager.instance.objectMinPos, StatusManager.instance.objectMaxPos, StatusManager.instance.fuelXRatio);
            RePositionObjcet(block, stages[i], StatusManager.instance.objectMinPos, StatusManager.instance.objectMaxPos, StatusManager.instance.blockXRatio);
        }
    }
 
    //스테이지를 움직입니다.
    //Move the stage
    private void StageMoves()
    {
        for (int i = 0; i < stages.Length; i++)
        {
            //게임 오버가 아니면 스테이지를 움직입니다.
            if (StatusManager.instance.gameSts != GAME_STS.OVER)
            {
                stages[i].GetComponent<Rigidbody2D>().velocity = new Vector2(-StatusManager.instance.GetSpeed(), 0f);
            } else
            {
                stages[i].GetComponent<Rigidbody2D>().velocity = Vector2.zero;
            }
 
            //스테이지 바닥 collider 기준으로 현재 위치를 탐색합니다.
            float _groundHorizonLength = stages[i].transform.GetChild(0).GetComponent<BoxCollider2D>().size.x;
 
            //스테이지 위치가 카메라를 벗어나는 경우 (-바닥 collider 크기 * 2), 왼쪽 끝에서 오른쪽 끝으로 재 위치 시킵니다.
            //스테이지 오브젝트 또한 랜덤으로 다시 배치합니다.
            if (stages[i].transform.position.x < -_groundHorizonLength * 2f)
            {
                stages[i].transform.position = (Vector2)stages[i].transform.position + new Vector2(_groundHorizonLength * (float)stages.Length, 0f);
                RePositionObjcet(fuel, stages[i], StatusManager.instance.objectMinPos, StatusManager.instance.objectMaxPos, StatusManager.instance.fuelXRatio);
                RePositionObjcet(block, stages[i], StatusManager.instance.objectMinPos, StatusManager.instance.objectMaxPos, StatusManager.instance.blockXRatio);
 
                //부스트, 함정(고양이 마리오) 배치 확률 (0 ~ 100%)
                int _boostDrop = Random.Range(0100);
                int _trapDrop = Random.Range(0100);
 
                if (_boostDrop < StatusManager.instance.boostProbability)
                {
                    float _width = stages[i].transform.position.x + stages[i].transform.GetChild(0).GetComponent<BoxCollider2D>().size.x;
                    RePositionObjcet(boost, stages[i], StatusManager.instance.objectMinPos, StatusManager.instance.objectMaxPos, _width);
                }
 
                if(_trapDrop < StatusManager.instance.trapProbabillity)
                {
                    float _width = stages[i].transform.position.x + stages[i].transform.GetChild(0).GetComponent<BoxCollider2D>().size.x;
                    RePositionObjcet(trap, stages[i], StatusManager.instance.objectMinPos, StatusManager.instance.objectMaxPos, _width);
                }
            }
        }
    }
 
    //스테이지에 오브젝트를 배치시키는 기능입니다.
    //대상 오브젝트(장애물, 아이템 등), 배치시킬 스테이지, 최소높이, 최대높이, 배치간격)
    void RePositionObjcet(GameObject obj, GameObject stage, float min, float max, float ratio)
    {
        //오브젝트의 이름을 바탕으로 오브젝트를 탐색, 삭제합니다.
        Destroy(stage.transform.Find(obj.transform.name).gameObject);
 
        //배치할 스테이지의 넓이를 저장합니다.
        float _width = stage.transform.position.x + stage.transform.GetChild(0).GetComponent<BoxCollider2D>().size.x;
 
        //Hierachy창의 정리를 위해서 만든 빈 오브젝트 입니다.
        GameObject _subObj = new GameObject(obj.transform.name);
 
        //최소, 최대 높이 사이에서 일정 간격 비율로 오브젝트가 생성됩니다.
        //생성된 오브젝트는 정리용 빈 오브젝트 자식으로 들어가 Hierachy창이 깔끔해집니다.
        for (float i = stage.transform.position.x; i < _width; i += ratio)
        {
            GameObject tempBlock = Instantiate(obj, new Vector2(i, Random.Range(min, max)), Quaternion.identity);
            tempBlock.transform.parent = _subObj.transform;
            _subObj.transform.parent = stage.transform;
        }
    }
}
 
cs

 

첫번째 글에서 말씀드렸던 FlappyBirdStyleExample 자습서 프로젝트의 스테이지 전환 부분 아이디어를 가져왔습니다.

다만 FlappyBirdStyle와는 다르게 제가 만든 게임은 스테이지 전환이 굉장히 빠르므로 스테이지가 로드 되기 전에 화면이 넘어갈 가능성이 높습니다.

다수의 스테이지 오브젝트를 연결하여 스테이지를 길게 만들 수도 있는 것도 고려해야겠죠?

 

때문에 Stage 오브젝트를 많이 추가하는 상황이 발생해도, 스크립트를 변경하지 않아도 되도록 설계하였습니다.
(Inspector에서 stage 배열 숫자만 늘려주고 스테이지 오브젝트를 추가해주면 ok입니다)

 

그리고 FlappyBirdStyle에서 초기 위치를 SceneView에서 배치한 위치를 바탕으로 움직이는게 불안정 하다는 생각이들어서

(계속 수정하다보면 위치가 틀어질 수 있고, 게임에서 스테이지 초기 위치 변경이 필요없기 때문에)
InitStagePosition 메소드로 초기 기준 위치를 실행 시 직접 원점(0, 0) 으로 설정해주었습니다.

 

 

소스코드에서 계속 나오는 StatusManager의 경우 게임의 모든 수치를 한번에 관리하는 싱글톤 패턴 스크립트 입니다.
예를들어, 67~70번 줄의 부스트와 함정 배치 변수는 StatusManager에서 각각 Boost, Trap Probabillity를 조정하여 등장 비율을 조절할 수 있습니다.

 

 

2가지 포인트 설명을 위해 인게임 실행 시 Scene, Hierachy Window 스크린샷을 가져왔습니다.

1. stage의 자식의 첫번쨰 (transform.GetChild(0))은 스테이지의 바닥 오브젝트를 말합니다.

(스크린샷의 StageBlock(1) 바로 아래 GroundDown을 말합니다)


2. 89번 줄에서 시작하는 RePositionObject 메소드의 Hierachy 오브젝트 정리는 스크린샷의 오른쪽 Hierachy 처럼 정리가 됩니다.

 

그럼 오브젝트 들의 겹침을 어떻게 했느냐? 

..그냥 접촉한 오브젝트가 플레이어가 아님 삭제 되도록 하였습니다 ㅎㅎ...   

 

ex) 블럭 오브젝트의 AutoDestroyer 스크립트에서 : 

    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.gameObject.tag != "Player")
        {
            Destroy(gameObject);
        }
    }

Posted by Haedo
,