ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (29) 메인맵 제작#5 + 스테이지 제작#2
    Galaxy Ball/2. 싱글플레이 - 스토리모드 2024. 6. 16. 03:29

     

    한가지 추가해야 할 기능이 있다. 이 게임 자체 컨셉이 우주라 맵을 굉장히 넓게 잡았다.

    근데 게임을 클리어하고 맵으로 돌아왔을때 스폰장소는 항상 똑같다는것

    매번 같은 장소에서 시작해 저 멀리 떨어져있는 스테이지를 찾아가는것은 상당히 번거롭기에

    이번엔 스테이지에 입장하기 전, 플레이어의 위치정보를 기억해주고 있다가 다시 맵으로 돌아왔을때

    있었던 위치로 돌아갈수 있도록 해보겠다

     

    우선 씬변환이 일어나더라도 플레이어의 위치정보는 계속 기억되어야 하기에 위치값을 싱글톤으로 잡아야한다

    그래서 원래는 플레이어를 컨트롤 하는 스크립트에서 위치정보 관리를 하려고 했었는데

    어차피 싱글톤을 써야 한다면 이미 싱글톤을 사용하고 있는 GameManager에서 하는게 더 나을것같다

     

    public class StageGameManager : MonoBehaviour
    {
        public static StageGameManager instance = null;
        public int StageClearID = 1;
    
        private Vector3 clickPosition;
        public bool isDragging = false;
        public static float shotDistance;
        public static Vector3 shotDirection;
    
        private void Awake()
        {
            if (instance == null)
            {
                instance = this;
                DontDestroyOnLoad(gameObject);
            }
            else
            {
                if (instance != this) 
                    Destroy(this.gameObject);
            }
        }
        
        ......

     

    StageClearID를 싱글톤으로 저장중인 GameManager.

     

    이제 해야할걸 순서대로 적어보겠다

     

    1. GameManager에서 플레이어 오브젝트를 찾아 실시간으로 위치정보를 출력시키기

     

    2. PlayerPosition으로 변수를 하나 만들어 그 안에 실시간으로 변화하는 플레이어의 위치값을 넣어주기

    (이렇게 해야 플레이버튼을 누르기 직전 마지막 플레이어의 위치값을 기억할 수 있다)

     

    3. 게임을 클리어하여 돌아오거나 일시정지로 돌아와서 맵 스테이지로 씬변환을 할때

    PlayerPosition이 기억하고 있던 위치값을 불러와 그곳에 플레이어를 리스폰 시키기

     

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

     

    계획이 변경됐다. 위치정보를 외워두었다가 그 장소 그대로 스폰하는것이 아닌

    스폰이 될때마다 전부 랜덤한 위치에서 생성되도록 하겠다. 그렇게 한다면 해야할게 조금 달라진다

     

    1. 게임을 클리어하여 돌아오거나 일시정지로 돌아와 맵 스테이지로 씬변환을 할때

    맵 스테에지 내에서 랜덤한 위치값을 받아 가져와 플레이어를 배치시키기

     

    2. 6~15 스테이지 16~65 스테이지 모두 스폰되는 반경을 전부 다르게 잡아준다

     

    오히려 방법은 더 간단해졌다. 이것 역시 StageClearID에 따라 x,y축값을 다르게 받아주면 된다

     

    그럼 가장 먼저 할건 각각 맵의 사이즈를 알아야한다. 랜덤으로 값을 받을 x,y축 범위를 알아야하기 때문

     

    아 참고로 맵의 크기를 더 넓혔다. 기존 크기의 정확히 4배이다

     

    -460, -330                       -320, -330
    -460, -470                       -320 -470         

     

    우선 6~15번 스테이지까지의 활동반경 범위이다

     

    -1150 ,  300                      400, 300
    -1150, -1150                    400, -1150

     

    그리고 이건 전체 맵 크기이다

     

    이제 코드를 수정해주자. 맵 씬을 불러왔을때 메인 플레이어 오브젝트를 찾아 위치를 변경시켜주면 되니

    씬 전체를 관리하는 Stage Game Manager에서 수정해주면 된다

     

       private void Start()
       {
           StageGameManager gameManager = FindObjectOfType<StageGameManager>();
           if (gameManager != null && gameManager.StageClearID >= 6 && SceneManager.GetActiveScene().name == "Stage")
           {
               Debug.Log("5번 스테이지까지 클리어했으므로 Main Scene으로 넘어갑니다");
               SceneManager.LoadScene("Main Stage");
           }
    
           GameObject mainPlayer = GameObject.Find("Main Player");
           if (mainPlayer != null)
           {
               if (gameManager.StageClearID <= 15)
               {
                   randomX = Random.Range(-460, -320);
                   randomY = Random.Range(-470, -330);
               }
               else if (gameManager.StageClearID > 15)
               {
                   randomX = Random.Range(-1150f, 400f);
                   randomY = Random.Range(300f, -1150f);
               }
               mainPlayer.transform.position = new Vector3(randomX, randomY, mainPlayer.transform.position.z);
           }
       }

     

    씬을 불러오자마자 실행해야 되니 Start 메서드 안에 넣어주었고, 위에 설명한대로 StageClearID에 따라

    랜덤으로 받을 x,y축값의 범위를 지정해주었다

     

     

     

    결과를 확인해보자. StageClearID값이 15일땐 첫번째로 지정한 반경안에서 위치값이 정해지고

    16이 되는순간 전체맵안에서 랜덤하게 돌아다니는것을 확인할수 있다

     

    근데 한가지 문제가 생겼다. 위 동영상처럼 내가 직접 게임을 실행했다 껐다를 반복할땐 원하는것처럼 배치가 되지만

    게임내에서 씬변환을 할땐 계속 맨 처음 지정해준 장소에서 시작이 된다는것. 

    잠시 생각해보니 

     

    게임이 실행되면 GameManager 오브젝트 자체가 삭제가 되지 않기때문에

    값이 랜덤으로 들어가지 않았던것이었다

     

     private void Start()
     {
         rigid = GetComponent<Rigidbody2D>();
         stageGameManager = FindObjectOfType<StageGameManager>();
    
         if (stageGameManager.StageClearID <= 15)
         {
             randomX = Random.Range(-460, -320);
             randomY = Random.Range(-470, -330);
         }
         else if (stageGameManager.StageClearID > 15)
         {
             randomX = Random.Range(-750f, 0f);
             randomY = Random.Range(0f, -850f);
         }
         gameObject.transform.position = new Vector3(randomX, randomY, gameObject.transform.position.z);
     }

     

    그래서 위 기능을 구현하는 코드를 플레이어 공을 컨트롤하는 StageBallControl로 옮겨주었다. 

    Main Player에서 gameobject로 변한것말고는 큰 차이점 없다

     

    아 그리고 범위를 조금 좁혀주었다. 너무 넓은 범위내에서 랜덤생성이 되어도 피로감이 커질것 같다

     

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

     

    이번엔 보다 더 현실적인 태양계를 구현해보려고 한다

     

    지금까지는 정적인 모습으로 모든 행성들이 정지 상태였지만

    지금부터는 태양을 중심으로 다른 행성들이 이동하게 만들어 볼 예정이다

     

    using UnityEngine;
    
    public class StageState : MonoBehaviour
    {
        public GameObject StageStart;
        public GameObject StartButton;
        public GameObject Clearhere;
        public int stagenum;
        public static int chooseStage;
        private bool isclear;
        private StageGameManager gameManager;
        private SpriteRenderer spriteRenderer;
    
        public bool isturn = false; 
        public float rotationSpeed = 1f; 
        public float radius = 5f;
        private float initialAngle; 
    
        private Vector3 initialPosition;
        private float angle = 0f; 
    
        void Start()
        {
            string namePart = gameObject.name.Substring(5, 2); // 6번째와 7번째 문자를 추출
            this.stagenum = int.Parse(namePart);
            gameManager = StageGameManager.instance;
            int stageClearID = gameManager.StageClearID;
    
            spriteRenderer = GetComponent<SpriteRenderer>();
            initialPosition = transform.position; // 초기 위치 저장
            initialAngle = Random.Range(0f, 360f); // 초기 각도를 랜덤으로 설정
            angle = initialAngle; // 초기 각도로 설정
    
            if (stageClearID < this.stagenum)
            {
                isclear = false;
                spriteRenderer.color = new Color32(100, 100, 100, 255);
            }
            else if (stageClearID == this.stagenum)
            {
                isclear = true;
                spriteRenderer.color = new Color32(100, 100, 100, 255);
    
                if (Clearhere != null)
                {
                    Instantiate(Clearhere, transform.position, Quaternion.identity, transform);
                }
            }
            else
            {
                isclear = true;
                spriteRenderer.color = new Color32(255, 255, 255, 255);
            }
        }
    
        void Update()
        {
            if (isturn)
            {
                // 원을 그리며 이동
                angle += rotationSpeed * Time.deltaTime; 
                if (angle > 360f) angle -= 360f; // 각도가 360도를 넘지 않도록 조정
    
                float x = Mathf.Cos(angle) * radius;
                float y = Mathf.Sin(angle) * radius;
    
                transform.position = initialPosition + new Vector3(x, y, 0f);
            }
        }
    
        private void OnTriggerStay2D(Collider2D collision)
        {
            if (collision.gameObject.tag == "StageBall")
            {
                Debug.Log("스테이지 플레이창을 띄웁니다");
                StageStart.gameObject.SetActive(true);
                if (!isclear)
                {
                    StartButton.gameObject.SetActive(false);
                }
                else if (isclear)
                {
                    StartButton.gameObject.SetActive(true);
                }
            }
            chooseStage = stagenum;
            Debug.Log("chooseStage : " + chooseStage);
            FindObjectOfType<ShowStageBox>().UpdateStageInfo(chooseStage);
        }
    
        private void OnTriggerExit2D(Collider2D collision)
        {
            if (collision.gameObject.tag == "StageBall")
            {
                StageStart.gameObject.SetActive(false);
            }
        }
    }

     

    모든 스테이지 즉, 행성을 통제하는 코드이다. 원래는 움직임에 관여하는 코드가 없었으나

    이번에 추가했다. 행성에 따라 도는 궤도, 공전 속도가 전부 달라야하니 이동속도와 반지름 길이는 내가 직접

    수정 가능하도록 하였고, initialangle은 게임이 시작될때마다 모든 행성들이 같은 위치에서 공전을 시작하면 진부하니

    이 변수만큼은 내가 통제하지도, 초기값을 정해주지도 않고 매번 랜덤으로 값을 받아 위치를 랜덤배치 하도록 했다

     

     

     

    오...이제 진짜 그럴싸한 태양계 모습이 구현되었다

     

    한가지 아쉬운건 달이다. 달은 고증에 맞추려면 지구에 딱 달라붙어 지구 주변을 공전해야 하는데 따로 공전한다는것

    물론 스크립트를 수정하거나 하나 더 만들어 붙일수 있지만 스토리상 붙이지 않기로 하겠다

     

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

    <6>   첫번째 아이템
    <10> 두번째 아이템

    <14> 탱커형 유닛 첫 등장

    <17> 세번째 아이템
    <21> 네번째 아이템

    <23> 특수형 유닛 첫 등장

    <25> 첫번째 중간보스
    <34> 다섯번째 아이템 + 보스형 유닛 첫 등장

    <43> 힐러 유닛 첫 등장

    <45> 두번째 중간보스

    <52> 혼합형 유닛 첫 등장
    *10번 스테이지부터는 5의 배수마다 하드 스테이지*

     

    스테이지 제작은 하나하나 글로 쓸 순 없으니 중간중간 보고하듯이 적겠다

     

    현재 절반가량 제작해놓은 상태이다. 오늘도 열심히 달려서 최대한 만들어보겠다

     

    아 추가사항 하나더.

     

     

    무적 아이템인 Invinvible.

    원래는 닿는 구체나 고정 물체를 제거하지만 이제부터는 적 유닛도 제거한다

     

    사실 어떤 종류와 상관없이 제거해주니 밸런스가 갑자기 무너진 느낌이지만 대신 아이템이 한번 생성되는데

    걸리는 시간을 더 길게 잡는걸로 하겠다

     

    오랜 시간 기다려서 아이템 생성 + 1/5의 확률을 뚫고 무적 아이템 + 아이템 획득 + 아이템을 날려

    구체와 고정물체를 피해 정확히 적 유닛을 맞춤

     

    솔직히 이 정도 확률이면 밸런스가 나쁘지 않다고 생각한다. 그리고 이런 아이템을 하나 추가하면

    앞으로 스테이지를 만들때 조금 더 난이도를 올리는게 부담되지 않는다

     

    하지만 이것도 적 최종보스와 중간보스급 유닛을 제거할 수 없다

    ....사실 제거 가능하다

     

     if (coll.gameObject.tag == "P1ball" || coll.gameObject.tag == "P2ball" || coll.gameObject.tag == "P1Item" || coll.gameObject.tag == "P2Item"
         || coll.gameObject.tag == "EnemyBall" || coll.gameObject.tag == "Item" || coll.gameObject.tag == "Gojung" || coll.gameObject.tag == "EnemyCenter")
     {
         Destroy(coll.gameObject);
         Destroy(gameObject);
     }

     

    핵심이 되는 이 코드는 변함이 없기 때문. 물론 이 코드를 만져 중간보스급은 파괴할수 없도록 할수 있지만

    그럼 코드가 너무 복잡해지고 이것저것 건드릴것이 많아지기에 더 간단한 방법을 선택했다

     

     

     

    걍 중간보스 나오는 45번 스테이지에는 무적 아이템을 생성시켜주지 않는것

    생각만 해도 복잡한 과정들을 모두 거치느니 가끔은 완벽하지 않더라도 이렇게 코드 3줄로 해결시키는게 더 좋을것같다

Designed by Tistory.