기능 구현

아이템 랜덤 스폰 / 손전등 기능 구현

Cadi 2025. 4. 27. 02:49

목차

     

     

       

      아이템 랜덤 스폰

       

      using System.Collections;
      using System.Collections.Generic;
      using Unity.VisualScripting;
      using UnityEngine;
      
      public class ItemSpawnManager : MonoBehaviour
      {
          public ItemSpawnArea[] spawnAreas;
          public ItemData[] ItemDatas;
      
          public ItemDataBase itemDataBase;
      
          public List<(int, ItemData)> SpawnItemWithNumber = new List<(int, ItemData)>();
      
          List<ItemSpawnPoint> itemSpawnPoints = new List<ItemSpawnPoint>();
      
      
          public void Start()
          {
              foreach (var area in spawnAreas)
              {
                  itemSpawnPoints.AddRange(area.itemSpawnPoint);
              }
      
      
              // 앞에서 N개만 사용
      
              for (int i = 0; i < SpawnItemWithNumber.Count; i++)
              {
                  // 랜덤으로 섞어버려 ~~
                  for (int j = 0; j < itemSpawnPoints.Count; j++)
                  {
                      var temp = itemSpawnPoints[j];
                      int randomIndex = Random.Range(0, itemSpawnPoints.Count);
                      itemSpawnPoints[j] = itemSpawnPoints[randomIndex];
                      itemSpawnPoints[randomIndex] = temp;
                  }
      
                  int currentItemNumber = SpawnItemWithNumber[i].Item1;
                  ItemData currentItemData = SpawnItemWithNumber[i].Item2;
                  GameObject currentPrefab = itemDataBase.itemPrefabs[currentItemData];
      
                  for (int j = 0; j < currentItemNumber; j++)
                  {
                      var spawnPoint = itemSpawnPoints[j];
                      Instantiate(currentPrefab, spawnPoint.transform.position, Quaternion.identity);
                      spawnPoint.isOccupied = true;
                  }
              }
          }
      }

      아이템 랜덤 스폰 코드를 처음 작성해봤다. 

      현재 코드에서 문제는, 
      1 isOccupied를 true로 설정한 후, 해당 스폰 포인트를 다음 배치 대상에서 제외하지 않아서 중복 배치가 발생할 수 있는 문제.

      2. 비효율적으로 여러 번 섞는 문제 .

       

      처음에는 스폰 기능을 한 번만 사용하려고 했기 때문에 중복 배치를 고려하지 않았었다.

      다만 만들고자 하는 게임의 진행도가 진척됨에 따라 아이템을 게임 진행도에 맞추어 생성해야 할 수도 있기 때문에 중복 배치를 고려해야 한다.

       

      두 번째 문제인 여러 번 섞는 문제는 한 번 섞었을 때 '완전히 랜덤'이란 것을 간과했다. 

      섞은 후에 아이템을 배치하면 정렬된 것으로 착각하고 다시 섞었다. 그러나 완전히 섞은 후 순차적으로 아이템을 배치하면 

      아이템 종류/위치가 랜덤으로 배치되기 때문에 한 번만 섞어주어도 좋다. 

       

      using System.Collections;
      using System.Collections.Generic;
      using Unity.VisualScripting;
      using UnityEngine;
      
      
      [System.Serializable]
      public class ItemSpawnData
      {
          public int count;
          public ItemData itemData;
      }
      
      public class ItemSpawnManager : MonoBehaviour
      {
          public ItemSpawnArea[] spawnAreas;
      
          public ItemDataBase itemDataBase;
      
          public List<ItemSpawnData> itemSpawnDatas = new List<ItemSpawnData>();
      
          
          [SerializeField]
          private List<ItemSpawnPoint> itemSpawnPoints = new List<ItemSpawnPoint>();
      
      
          public void Start()
          {
              foreach (var area in spawnAreas)
              {
                  itemSpawnPoints.AddRange(area.itemSpawnPoint);
              }
      
              // 랜덤으로 섞어버려 ~~
              for (int j = 0; j < itemSpawnPoints.Count; j++)
              {
                  var temp = itemSpawnPoints[j];
                  int randomIndex = Random.Range(0, itemSpawnPoints.Count);
                  itemSpawnPoints[j] = itemSpawnPoints[randomIndex];
                  itemSpawnPoints[randomIndex] = temp;
              }
              // 앞에서 N개만 사용
      
              int spawnCount = 0;
      
              for (int i = 0; i < itemSpawnDatas.Count; i++)
              {
                  int currentItemNumber = itemSpawnDatas[i].count;
                  ItemData currentItemData = itemSpawnDatas[i].itemData;
                  if (!itemDataBase.itemPrefabs.TryGetValue(currentItemData, out GameObject prefab))
                  {
                      Debug.LogWarning($"프리팹을 찾을 수 없습니다: {currentItemData}");
                      continue;
                  }
      
                  GameObject currentPrefab = itemDataBase.itemPrefabs[currentItemData];
      
                  for (int j = 0; j < currentItemNumber; j++)
                  {
                      // 모든 스폰 위치를 다 사용했다면 (그럴ㅇ 일 없겠지만)
                      if (spawnCount >= itemSpawnPoints.Count)
                      {
                          Debug.LogWarning(" 스폰 포인트 꽉 참");
                          return;
                      }
      
                      var spawnPoint = itemSpawnPoints[spawnCount];
                      Instantiate(currentPrefab, spawnPoint.transform.position, Quaternion.identity);
                      spawnPoint.isOccupied = true;
                      spawnCount++;
                  }
              }
          }
      }

       

      완성된 함수이다. 

      추가적으로 동적으로 아이템을 생성하고 싶다면 ( 게임 진행도에 따라 ) 아이템을 스폰하는 함수를 따로 묶어 실행시킬 예정이다.

       

       

       

       

       


       

       

       

      손전등 관련

       

      구현해야 할 기능은 다음과 같다. 

      •  손전등 배터리 + UI에 표시
      •  손전등 ON/OFF 기능
      •  손전등 충전 기능 ( 다른 함수에서 실행될 ) 
      •  동적으로 손전등의 밝기/크기/거리/색을 조절하는 기능 ( 테스트를 위한 ) 

       

       

      [CreateAssetMenu(menuName = "Inventory/ItemData/Consumable/Battery")]
      public class BatteryItem : ConsumableBase
      {
          // public override void Consume()
          // {
          //     Debug.Log("잘못된 Consume사용");
          // }
          //
          // public virtual void Consume(FlashLight flashLight)
          // {
          //     flashLight.rechargeBattery();
          // }
        
          public override void Consume(object target)
          {
              if (target is FlashLight flashlight)
              {
                  flashlight.rechargeBattery();
              }
              else
              {
                  Debug.LogError("BatteryItem은 FlashLight에만 사용할 수 있습니다.");
              }
          }
      }

       

      각각의 아이템들마다 들어온 타겟 ( 아이템 효과를 적용할 타겟 ) 을 기준으로 함

       

      public void ConsumeItem(Object target)
      {
          if (currentItemData != null && currentItemData.isConsumable && currentItemData.consumable != null)
          {
              switch (currentItemData.itemType)
              {
                  case ItemType.Battery:
                      if (currentItemData.consumable is BatteryItem batteryItem)
                      {
                          batteryItem.Consume(target);
                      }
                      break;
                  
              }
              inventorySlotData[currentItemIndex].Clear();
              _inventoryItemImages[currentItemIndex].SetImage(null);
          }
      }

       

      인벤토리는 각각의 아이템 효과를 적용할 타겟을 몰랐으면 좋겠다. 

      인벤토리가 예를 들어 HP bar라던지, FlashLight를 들고 있으면 이상할 것이다. 

       

      if (Input.GetKeyDown(KeyCode.Q))
      {
          Object target = GetTargetForCurrentItem();
          inventory.ConsumeItem(target);
      }

       

      마찬가지로 Interaction Manager 코드에서는 그냥 Q를 누르면 현재 들고 있는 아이템을 소비해 ! 만 했으면 좋겠다. 

      다만, Interaction Manger에서 Hp Bar, FlashLight와 같은 소비한 아이템이 적용될 클래스들을 모두 참조하고 있으므로 

      알맞은 참조를 넘겨주어야 한다. 이를 위해서 함수를 하나 만들었다. 

       

      private Object GetTargetForCurrentItem()
      {
          if (inventory.currentItemData.itemType == ItemType.Battery) return flashLight;
      
          return null;
      }

       

      위와 같은 함수로 알맞은 타입을 넘기기만 하고  적용은 각각의 아이템들에서 받아온 타겟을 해서 형변환 해서 각자 알아서 한다. 

       

      + 원래 코드에서는 UI가 인벤토리를 들고 있는 등 말이 안되는 요소가 많아서 정리했다.


      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class FlashLight : MonoBehaviour
      {
         [Header("UI / Light 참조")]
         [SerializeField] private FlashLightUI flashLightUI;
         [SerializeField] private Light flashLight;
      
         
         [Header("조정 가능한 옵션")]
         [SerializeField] private Color lightColor = Color.white;
         [SerializeField, Range(0f, 10f)] private float lightIntensity = 1f;
         [SerializeField, Range(0f, 100f)] private float lightRange = 10f;
         [SerializeField, Range(1f, 179f)] private float spotAngle = 60f;
         
         public bool flashLightOn = false;
         private bool _hasBattery = true;
         
         [SerializeField] private float fullBattery = 20f;
         [SerializeField] private float currentBattery = 20f;
         
         public void Start()
         {
            flashLight.enabled = false;
            
            #if UNITY_EDITOR
            ApplyLightSettings();
            #endif
            
         }
      
         public void Update()
         { 
            if (flashLightOn && _hasBattery)
            {
               flashLight.enabled = true;
               Debug.Log($"Current battery is {currentBattery}");
               // 시간이 경과함에 따라 배터리 감소
               currentBattery -= Time.deltaTime;
               flashLightUI.UpdateFlashLightImage(currentBattery, fullBattery);
               if (currentBattery <= 0) _hasBattery = false;
            }
            else
            {
               flashLight.enabled = false;
            }
            #if UNITY_EDITOR
            ApplyLightSettings();
            #endif
         }
         
         private void ApplyLightSettings()
         {
            if (flashLight == null) return;
      
            flashLight.color = lightColor;
            flashLight.intensity = lightIntensity;
            flashLight.range = lightRange;
            flashLight.spotAngle = spotAngle;
         }
         
         
         public void ToggleFlashLight()
         {
            flashLightOn = !flashLightOn;
         }
         
         
         public void rechargeBattery()
         {
            currentBattery = fullBattery;
            _hasBattery = true;
         }
       
      
      
         
      }
      

       

       

      원래는 UI에서 CurrentBattery와 FullBattery를 갖고 있었는데, 이번에 손전등을 실제로 만들면서 이상한 것 같아서 바꿨다.

      #if UNITY_EDITOR 전처리기를 통해 게임이 실행 중일 때는 불필요한 매 프레임 광원 설정 업데이트를 방지하고, 에디터 모드에서만 광원 설정을 실시간으로 조정할 수 있도록 했다. 이는 퍼포먼스를 고려한 설계다.

       

       


      간단 리뷰

       

      생각보다 오래 걸렸다. 기본적인 기능들을 전부 구현해 두어서 금방 할 줄 알았는데 3 ~ 4시간은 걸린 듯하다. 그 이유는 이미 있던 코드를 상당 부분 리팩토링했기 때문이다. 구조가 이상하고, 의존성도 높던 코드들을 그나마 의존성을 줄였다. 아마 다음에는 FlashLight에 있는 키 누르는 것도 옮길 듯하다. (금방 하니까 미뤄두었다 ㅎ..) 그래도 이런 방식에 대해서 조금... 아주 조금은 감을 잡은 듯하다.