ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (19) 아이템 #UI
    Galaxy Ball/1. 멀티플레이 - 대전모드 2024. 4. 4. 17:33

    시작하기 전, 고정구체 아이템은 디자인에 변화를 주었다. 기존 고정구체랑 완전히 똑같이 디자인을 가져가다보니

    플레이 도중에 너무 헷갈릴것 같다

     

     

    (18) 아이템 #랜덤 생성

    우선 시작하기전에 아이템을 랜덤 생성시킬 스크립트는 어디에 들어가야할지부터 생각해보겠다 using System; using UnityEngine; public class GameManager : MonoBehaviour { public GameObject P1ballPrefab; public GameObject P

    sangeun00.tistory.com

    1. 10~15초마다 플레이 영역 한정 랜덤한 곳에 구체를 생성

    2. 그 구체는 내가 정해놓은 확률에 따라 구체의 이미지가 달라짐

    3. 내가 발사한 구체가 랜덤생성 구체에 충돌하는 순간 사라짐(발사가 아닌 팽창되는 과정에서 충돌하는것도 충돌판정)

    4. 사라진 구체(먹은 아이템)는 내가 지정해놓은 위치에 아이템 보관함에 버튼으로 활성화

    5. 보관 가능한 아이템의 갯수는 최대 3개. 먹은 순서대로 아이템창에 보관

    6. 아이템 기능구현

    7. 내 턴에 보관한 아이템을 클릭시 아이템 기능이 구현되어 게임에 적용됨

     

    지난 글에서 2번까지 구현했으니 이번 글에서는 3~5번까지 구현해보겠다

    사실상 아이템 파트를 가장 늦춘것도, 어려워한 이유도 이 3~5번 때문이었다. 정확히는 4,5번 때문이었지만

    그래도 한번 해보도록 하겠다

     

    3. 내가 발사한 구체가 랜덤생성 구체에 충돌하는 순간 사라짐(발사가 아닌 팽창되는 과정에서 충돌하는것도 충돌판정)

     

    자 그럼 이 충돌판정시 아이템을 없애는 스크립트는 어디에 넣어주면 될까.

    지금 떠오른 생각은 이 프리팹들을 전부 복붙하여 한쌍을 더 만든뒤

    생성 아이템용 & 기능 구현용으로 만드는게 어떨까 싶다. 더 좋은 방법이 있을지도 모르지만 지금 떠오른건 이게 최선이다

     

     

    아이템 관련 프리팹을 저장하는 폴더를 2개 만들어준뒤 Generate에는 랜덤 생성되는 아이템 프리팹

    Fire 폴더에는 실제 기능구현에 쓰일 프리팹을 그대로 복붙하여 넣어주었고

    구별을 위해 모든 오브젝트 이름뒤에 Fire의 F를 붙여주었다

     

    지금 당장은 아니지만 미리 하나 더 추가하겠다

    이건 아이템 아이콘으로 쓰는용 프리펩들이다. 이름 뒤에 모두 Icon의 I를 붙여주었다

     

    즉 총 3가지 용도로 똑같은 프리펩들을 복붙하여 정리한것이다

    1. 플레이 중 랜덤 생성용

    2. 1번의 아이템을 먹었을때 아이템창에 뜨는 아이콘용

    3. 2번의 아이콘을 사용했을때 실제로 게임에 나가는 기능구현용   

     

    좀 더 쉽게 보기 위해 그림으로 설명하자면 이런 느낌인것이다

    물론 이걸 정말 하나하나 다 복붙해서 쓰는게 맞는건가 싶다. 하지만 1,2,3번의 기능이 각자 다 다르고,

    들어가는 스크립트가 다 다른데 이 방법이 아니면 어떻게 해야할지 감이 안잡히기에 이 방법을 채택했다

     

    이제 Generate 폴더에 있는 모든 프리팹에 들어갈 스크립트를 작성해주자

    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class GenerateItem : MonoBehaviour
    {
        public AudioSource GetItemSound;
    
        private void OnTriggerEnter2D(Collider2D collision)
        {
            GetItemSound.Play();
            Rigidbody2D otherRigidbody = collision.gameObject.GetComponent<Rigidbody2D>();
            if ((collision.gameObject.tag == "P1ball" || collision.gameObject.tag == "P2ball") && otherRigidbody != null)
            {
                Destroy(gameObject);
            }
        }
    }

     

    충돌하자마자 충돌구체의 rigidbody2D 정보를 가져온뒤 충돌구체의 태그가 "P1ball" 혹은 "P2ball"이면서

    그 구체의 rigidbody가 null값이 아니라면 (=팽창 후 고정 상태가 아니라면) 아이템 프리팹을 파괴시킨다

     

    물론 아이템 획득 오디오 소스도 추가해주었다

     

    충돌 판정을 위해 모든 프리팹에 Circle Collider를 입히고 Is Trigger에 체크해준뒤 스크립트도 넣어주자

     

    3번에 적어둔것처럼 날아가면서 충돌 혹은 정지 뒤 팽창하면서 충돌하는것까지 전부 충돌판정으로 처리된다

    그리고 이미 정지되어 팽창까지 끝나버린 구체 위에 아이템이 생성되는 경우에는 충돌판정이 일어나지 않는것을 볼 수 있다

     

    4. 사라진 구체(먹은 아이템)는 내가 지정해놓은 위치에 아이템 보관함에 버튼으로 활성화

    5. 보관 가능한 아이템의 갯수는 최대 3개. 먹은 순서대로 아이템창에 보관

     

    이제 내가 가장 어려워하는 UI 시간이다. 우선 아이템 보관함을 만들어야하는데 이 위치 고민하는데 시간이 많이 들었다

    밖으로 빼거나, 안으로 집어넣거나 아니면 본인 firezone 활성화될때만 같이 활성화되는 방법도 생각해보았다

     

    결국 최종으로 결정한건 공간은 따로 만들지 않고 플레이공간 안에 아이템 UI를 배치하되,

    본인이 사용할 수 있는 아이템 목록은 본인의 firezone이 활성화 될때 함께 활성화 되는것으로 하겠다

     

    우선 아이템 저장고 느낌이 나는 심플한 아이콘을 만들고 싶은데 아무리 찾아봐도 내가 생각하는게 나오질않아

    내가 직접 만들기로 했다

     

    그림판으로 간단하게 만들어준뒤 투명하게 지워주자. 굳이 오른쪽과 아래에 간격을 둔 이유는

    딱 맞춰 지우니까 밑이 이렇게 되서...이유는 모르겠다

     

    아무튼 사이즈 조절해준뒤 프리팹으로 만들어 배치하면 이런식으로 되는것이다

     

    이제 각자의 firezone이 활성화 될때만 본인의 아이템창이 뜨도록 해보겠다

    firezone을 제어하는 곳은 GamaManager이기에 그 스크립트에서 firezone이 활성화/비활성화 되는곳에

    아이템창도 낑겨넣어주면 된다

     

    public class GameManager : MonoBehaviour
    {
        public GameObject P1ballPrefab;
        public GameObject P2ballPrefab;
        public GameObject P1firezone;
        public GameObject P2firezone;
        public GameObject P1Itemsave;
        public GameObject P2Itemsave;
        
                 ..........
                 
                 
    foreach (Collider2D collider in colliders)
     {
         if (P1FireMode && collider.gameObject == P1firezone)
         {
             Instantiate(P1ballPrefab, clickPosition, Quaternion.identity);
             P1FireMode = false;
             P2FireMode = true;
             isDragging = true;
             P1firezone.gameObject.SetActive(false);
             P2firezone.gameObject.SetActive(true);
             P1Itemsave.gameObject.SetActive(false);
             P2Itemsave.gameObject.SetActive(true);
             break;
         }
         if (P2FireMode && collider.gameObject == P2firezone)
         {
             Instantiate(P2ballPrefab, clickPosition, Quaternion.identity);
             P1FireMode = true; 
             P2FireMode = false;
             isDragging = true;
             P1firezone.gameObject.SetActive(true);
             P2firezone.gameObject.SetActive(false);
             P1Itemsave.gameObject.SetActive(true);
             P2Itemsave.gameObject.SetActive(false);
             break;
         }

     

    오브젝트 이름은 P1, P2ItemSave 로 지정했다

     

     

     

    뭔가 전체적으로 미니멀하고 심플한 느낌이 나길 원했는데 아이템 인벤이 들어오면서

    뭔가 약간 지저분해진것 같은 느낌이지만 우선은 이렇게 쓰기로 하자

     

    이제 아이템을 먹었을때 본인의 아이템 인벤에 아이템이 추가되어야한다

    P1 기준 밑에서부터 1,2,3번 자리라고 했을때 처음 먹은 아이템은 무조건 1번,

    다음 먹은 아이템은 그 다음번인 2번에 들어가야하며, 만약 1,2,3번이 모두 차있는 상태에서 2번 아이템을 쓴다면

    2번 자리만 비게 되며 다음으로 먹은 아이템은 자연스럽게 빈자리를 찾아 들어가야한다

    물론 1,3번이 동시에 비어있다면 당연히 다음 아이템은 1번 자리부터 채워야한다

     

    사실 이게 어려워보여서 그동안 아이템 구현을 미루고 미뤄왔던것이다

     

    도대체 이걸 어떻게하는게 좋을까 생각하다 문듯 골드메탈님의 유튜브 강의 하나가 떠올랐다

     

    내가 만들고 있는 게임과 전혀 관련도 없는 장르의 슈팅게임이지만 이 영상에서 보면

     

    임의의 위치에 빈 오브젝트를 만들어 번호를 붙인뒤 이곳에서 적 비행기를 생성하여

    내려오게 하는 식이다. 이걸 내가 쓰려는 아이템에 대입할 수 있지 않을까라는 생각이 들었다

     

    나도 영상에서 썼던 방식을 채택해보겠다

    똑같이 빈 오브젝트들을 각각의 아이템 인벤에 집어넣어주었다. P1은 파란색, P2는 빨간색이다

    폴더는 P1Item 안에 P1Item_Inven(아이템 저장공간)과 P1ItemIcon(아이템 아이콘이 생성될 장소)이 들어있는 식이다

     

    이제 저장공간과 아이콘이 생성될 위치도 만들어주었으니 해야할건 하나

    1. 아이템 충돌&파괴 시 파괴된 아이템의 정보를 아이콘 생성 장소에 넘겨주고,

    2. 아이콘 생성 장소가 넘겨받은 그 정보에 맞춰 알맞는 장소에 해당 아이템을 생성하여 주는것이다

     

    가장 먼저 해야할건 아까 짜둔, 충돌시 아이템을 파괴시키는 스크립트에서 먼저 수정해주자

    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class GenerateItem : MonoBehaviour
    {
        public AudioSource GetItemSound;
    
        private void OnTriggerEnter2D(Collider2D collision)
        {
            //GetItemSound.Play();
            Rigidbody2D otherRigidbody = collision.gameObject.GetComponent<Rigidbody2D>();
            if (collision.gameObject.tag == "P1ball" && otherRigidbody != null)
            {
                string destroyedObjectName = gameObject.name;
                P1ItemIcon itemIconScript = FindObjectOfType<P1ItemIcon>();
                itemIconScript.PrintDestroyedObjectName(destroyedObjectName);
                Destroy(gameObject);
            }
            if(collision.gameObject.tag == "P2ball" && otherRigidbody != null)
            {
                string destroyedObjectName = gameObject.name;
                P2ItemIcon itemIconScript = FindObjectOfType<P2ItemIcon>();
                itemIconScript.PrintDestroyedObjectName(destroyedObjectName);
                Destroy(gameObject);
            }
        }
    }

     

    경우를 2가지로 나누어주었다. 왜냐하면 아이템은 누가 먹느냐에 따라 아이템의 소유가 달라지기 때문

    P1ball에 충돌했을 경우 파괴되는 아이템 오브젝트의 이름을 받아 PrintDestroyObjectName이라는 곳에 넘겨주고

     

    여기서 PrintDestroyObjectName 메서드는 바로

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class P1ItemIcon : MonoBehaviour
    {
        public void PrintDestroyedObjectName(string objectName)
        {
            Debug.Log("P1ball에 의해 파괴된 오브젝트의 이름: " + objectName);
        }
    }
    -------------------------------------------------------------------------
    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class P2ItemIcon : MonoBehaviour
    {
        public void PrintDestroyedObjectName(string objectName)
        {
            Debug.Log("P2ball에 의해 파괴된 오브젝트의 이름: " + objectName);
        }
    }

     

    새로 만든 2개의 스크립트에 속한 메서드이다. 이것 역시 플레이어가 2명이고 아이템의 소유를 결정하는

    스크립트이기에 2개를 P1&P2ItemIcon이라는 이름으로 따로따로 만들어주었다

    그리고 디버그로 어느공이 아이템과 충돌했는지, 소유권을 알려줌과 동시에 아이템의 이름까지 출력해준다

     

    이 두 스크립트는 각각의 아이템 아이콘을 관리하는 폴더안에 넣어주었다

    이제 결과를 확인해보자

     

    정상적으로 작동하는것을 볼 수 있다. 

     

    이번엔 넘겨받은 아이템의 이름을 가지고 아이템 아이콘을 인벤에 추가해보도록...하려고 했으나

    지금 아이템만 3번을 복붙한 상태라 이름보다는 처음부터 태그를 각자 하나씩 만들어준뒤

    태그를 이용하는게 앞으로도 훨씬 편할것같으니 태그를 하나씩 지어주자

    참 이럴때 태그만큼 유용한 기능이 없는것 같다. 이제 이 태그들을 본인의 이름에 맞게 배정해준다

     

    코드들까지 전부 이름이 아닌 태그를 띄우는것으로 수정해준뒤 다시 한번 확인해보았다

    앞으로는 이 태그들로 전부 통일하겠다

     

    이제 아이템이 구체와 충돌하여 파괴되면, GenerateItem에서 파괴된 아이템의 태그를 P1&P2ItemIcon에 넘기게 되고

    그 태그를 참고하여 P1IconPlace1,2,3에 생성되도록 해보겠다

     

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class P1ItemIcon : MonoBehaviour
    {
        public GameObject P1IconPlace1;
        public GameObject P1IconPlace2;
        public GameObject P1IconPlace3;
        public GameObject[] P1Icon;

    가장 먼저 전역 변수에 IconPlace1,2,3을 생성하여 아까 만든 빈 오브젝트 자리들을 넣어주고

    생성할 아이템 아이콘은 배열로 만들어 하나씩 전부 넣어준다

     

     public void PrintDestroyedObjectName(string objecttag)
     {
         Debug.Log("P1ball에 의해 파괴된 오브젝트의 태그: " + objecttag);
    
         GameObject nextIcon = null;
    
         switch (objecttag)
         {
             case "Item_Durability":
                 nextIcon = P1Icon[0];
                 break;
             case "Item_Fasten":
                 nextIcon = P1Icon[1];
                 break;
             case "Item_Five_Round":
                 nextIcon = P1Icon[2];
                 break;
             case "Item_Force":
                 nextIcon = P1Icon[3];
                 break;
             case "Item_Invincible":
                 nextIcon = P1Icon[4];
                 break;
             case "Item_Plus1":
                 nextIcon = P1Icon[5];
                 break;
             case "Item_Plus2":
                 nextIcon = P1Icon[6];
                 break;
             case "Item_Random_number":
                 nextIcon = P1Icon[7];
                 break;
             case "Item_Random_skill":
                 nextIcon = P1Icon[8];
                 break;
             case "Item_Reduction":
                 nextIcon = P1Icon[9];
                 break;
             default:
                 Debug.LogWarning("Unknown objecttag: " + objecttag);
                 break;
         }

     

    그 다음은 받은 태그에 따라 위치에 생성될 아이콘 프리팹을 결정해주는 부분이다

    case문을 사용했고, nextIcon이라는 오브젝트 변수에 건네받은 프리팹 값을 넣어준다

     

    if (nextIcon != null && placedItemCount < 3)
    {
        switch (placedItemCount)
        {
            case 0:
                Instantiate(nextIcon, P1IconPlace1.transform.position, Quaternion.identity, P1IconPlace1.transform);
                break;
            case 1:
                Instantiate(nextIcon, P1IconPlace2.transform.position, Quaternion.identity, P1IconPlace2.transform);
                break;
            case 2:
                Instantiate(nextIcon, P1IconPlace3.transform.position, Quaternion.identity, P1IconPlace3.transform);
                break;
        }
        placedItemCount++;
    }
    else
    {
        Debug.Log("더 이상 아이콘을 생성할 수 없습니다.");
    }

     

    이제 이 nextIcon 오브젝트를 각각 맞는 위치에 넣어줄것이다. 

    P1IconPlace1부터 차례로 들어가며 1~3까지 전부 차있는 경우 더이상 아이콘을 생성할 수 없다는 문구를 출력한다

    P2역시 마찬가지이다. 코드에서 P1 => P2로 바꿔주어 똑같이 적용시키면 된다. 

     

    이제 결과를 확인해보자

     

     

    정상적으로 작동하는것을 확인할 수 있다

     

    이 아이콘 프리팹같은 경우는 랜덤생성 프리팹과는 달리 충돌이 아닌 클릭으로 파괴되어야한다

    그럼 이 아이콘 프리팹에만 적용될 스크립트를 생성해주자

     

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class ItemIconDestroy : MonoBehaviour
    {
        private Camera mainCamera;
        private RaycastHit2D hit;
        private void Start()
        {
            mainCamera = Camera.main;
        }
        private void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
                hit = Physics2D.GetRayIntersection(ray);
    
                if (hit.collider != null && hit.collider.gameObject == gameObject)
                {
                    Destroy(gameObject);
                }
            }
        }
    }

     

    ItemIconDestroy라는 이름으로 새로운 스크립트를 만들어주었다. 지금은 단순히 클릭시 파괴하는것만 구현해놨지만

    이제 여기에서도 마찬가지로 파괴와 동시에 아이템 기능을 구현하는 스크립트에

    파괴된 아이콘의 해당 정보, 정확히는 태그를 전달할 예정이다

     

    짜준 스크립트를 모든 아이콘 프리팹에 적용시켜준뒤, 충돌판정을 위해 Circle Collider도 넣어주자

    물론 구체와의 충돌은 방지하기 위해 Is Trigger는 체크해준다

     

     

    이제 클릭하면 아이콘이 사라지는것까지 확인할 수 있다.

    이 글은 여기서 끝내려 했으나 한가지 문제점을 발견했다

    만약 1,2,3번이 가득 차 있는 상태에서 1,2번 아이콘을 사용하여 1,2번 자리는 비고 3번만 차있다고 가정했을때

    한번 더 아이템을 먹으면 1,2번에 자동으로 아이콘이 들어가는게 아니라 더이상 생성할 수 없다는 문구가 출력된다

     

    단순히 3번 자리가 차 있다고 더이상 못들어가는게 아니라 알아서 빈자리를 찾아 들어가게 하는 시스템을 구현해보자

     

        public GameObject[] P1IconPlaces;

    우선 IconPlace도 배열 처리를 하여 배정해주었다

     

     if (nextIcon != null)
     {
         foreach (GameObject place in P1IconPlaces)
         {
             if (place.transform.childCount == 0)
             {
                 Instantiate(nextIcon, place.transform.position, Quaternion.identity, place.transform);
                 return;
             }
         }
    
         Debug.Log("더 이상 아이콘을 생성할 수 없습니다.");
     }

    그럼 다음 코드를 보자 이제 place라는 오브젝트 변수가 P1IconPlaces를 쭉 둘면서 들어갈수 있는곳을 돌아볼것이다

    이렇게되면 어디가 비었든 상관없이 빈 장소로 들어가게 될것이다

     

     

    영상을 보면 가운데 아이템을 사용하여 빈자리가 가운데 났을때, 다음 아이템을 먹는 순간 이 가운데로 들어가는것을

    확인할 수 있다

     

    가장 막막해하고 어려워하던 부분을 하나님 덕분에 성공시켜서 기쁘다ㅠ

    다음글에서는 본격적으로 아이템 기능을 구현시켜보겠다

Designed by Tistory.