아이템 랜덤 스폰 / 손전등 기능 구현
목차
아이템 랜덤 스폰
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에 있는 키 누르는 것도 옮길 듯하다. (금방 하니까 미뤄두었다 ㅎ..) 그래도 이런 방식에 대해서 조금... 아주 조금은 감을 잡은 듯하다.