시너지 아이콘 반영
가챠 시스템
시퀸스를 이용한 효과를 구현했으며, 오프셋을 넣어서 다양한 곳에 쓰일 수 있도록 확장성을 고려했다.
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;
public class ProfileIconSelected : MonoBehaviour
{
[Header("Target Panel")]
[SerializeField] private GameObject targetPanel;
[Header("Button")]
[SerializeField] private Button showButton;
[SerializeField] private Button closeButton;
[Header("Offset")]
[SerializeField] private float initScale = 1.0f;
[SerializeField] private float popScale = 1.2f;
[SerializeField] private float popDuration = 0.1f;
private void Start()
{
DOTween.Init();
showButton.onClick.AddListener(ShowPanel);
closeButton.onClick.AddListener(HidePanel);
targetPanel.transform.localScale = Vector3.one;
targetPanel.SetActive(false);
}
private void ShowPanel()
{
targetPanel.SetActive(true);
var seq = DOTween.Sequence();
seq.Append(targetPanel.transform.DOScale(popScale, popDuration).SetEase(Ease.OutQuad));
seq.Append(targetPanel.transform.DOScale(initScale, popDuration));
seq.Play();
}
private void HidePanel()
{
var seq = DOTween.Sequence();
targetPanel.transform.localScale = Vector3.one;
seq.Append(targetPanel.transform.DOScale(popScale, popDuration).SetEase(Ease.OutQuad));
seq.Append(targetPanel.transform.DOScale(initScale, popDuration));
seq.Play().OnComplete(() => { targetPanel.SetActive(false); });
}
}
가챠 시스템에 대해서 아직 틀이 나오지 않았지만 아래와 같은 조건으로 설계해보기로 했다.
이와 같은 기준으로 우선 아래와 같이 노말 70퍼센트, 레어 20퍼센트, 유니크 8퍼센트, 레전드리 2퍼센트로 설정하여 스크립터블 오브젝트를 만들었다.
using System;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Probability", menuName ="Data/Probability")]
public class ItemProbabilitySO : ScriptableObject
{
[field:SerializeField] public List<ProbableItems> ItemsProbability { get; private set; }
}
[Serializable]
public class ProbableItems
{
public Grade ItemGrade;
public float Probability;
}
다음으로 뽑아야 할 캐릭터 목록을 스크립터블 오브젝트로 정리했다. 여기서 효율성을 챙기기 위해 등록된 데이터를 등급별로 캐싱하는 기능과 캐릭터를 뽑는 기능을 추가했다.
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Character Database", menuName = "Database/CharacterDatabase")]
public class CharacterDatabase : ScriptableObject
{
[SerializeField] private List<UnitData> units = new List<UnitData>();
private Dictionary<Grade, List<UnitData>> _unitCache;
private void OnEnable()
{
BuildCache();
}
/// <summary>
/// Grade별 캐시 생성
/// </summary>
private void BuildCache()
{
_unitCache = new Dictionary<Grade, List<UnitData>>();
foreach (var unit in units)
{
if (!_unitCache.ContainsKey(unit.Grade))
{
_unitCache[unit.Grade] = new List<UnitData>();
}
_unitCache[unit.Grade].Add(unit);
}
}
/// <summary>
/// 특정 Grade에 해당하는 모든 UnitData 리스트 반환
/// </summary>
public List<UnitData> GetUnitsByGrade(Grade grade)
{
if (_unitCache == null) BuildCache();
return _unitCache.ContainsKey(grade) ? _unitCache[grade] : new List<UnitData>();
}
/// <summary>
/// 특정 Grade에 해당하는 UnitData 중 랜덤 1개 반환
/// </summary>
public UnitData GetRandomUnitByGrade(Grade grade)
{
if (_unitCache == null) BuildCache();
if (_unitCache.TryGetValue(grade, out var list) && list.Count > 0)
{
return list[Random.Range(0, list.Count)];
}
return null;
}
}
다음으로 해당 방식에 따라 확률을 반영하여 데이터를 반환해주면 된다.
using System;
using UnityEngine;
using UnityEngine.UI;
public class RandomGachaSystem : MonoBehaviour
{
[Header("Reference")]
[SerializeField] private CharacterDatabase _data;
[SerializeField] private ItemProbabilitySO _prob;
[Header("UI")]
[SerializeField] private Button _dailyButton;
[SerializeField] private Button _oneGachaButton;
[SerializeField] private Button _tenGachaButton;
// 확률 소수점 자릿수
[Header("ProbOffset")]
[SerializeField] private int digits = 0;
private WeightedRandom<Grade> _gradeRandom = new WeightedRandom<Grade>();
private void Awake()
{
RandomInit(_prob);
_dailyButton.onClick.AddListener(() => ItemSelect(1));
_oneGachaButton.onClick.AddListener(() => ItemSelect(1));
_tenGachaButton.onClick.AddListener(() => ItemSelect(10));
}
private void RandomInit(ItemProbabilitySO probability)
{
for(int i = 0; i < probability.ItemsProbability.Count; i++)
{
Grade grade = probability.ItemsProbability[i].ItemGrade;
int value = (int)(probability.ItemsProbability[i].Probability * (int)Math.Pow(10, digits));
_gradeRandom.Add(grade, value);
}
}
private void ReturnData()
{
Grade grade = _gradeRandom.GetRandomItem();
UnitData unit = _data.GetRandomUnitByGrade(grade);
Debug.Log($"뽑힌 등급: {grade}, 캐릭터: {unit?.Name}");
}
private void ReturnDataBySub()
{
// grade에 해당하는 캐릭터를 랜덤으로 뽑기
Grade grade = _gradeRandom.GetRandomItemBySub();
UnitData unit = _data.GetRandomUnitByGrade(grade);
Debug.Log($"뽑힌 등급: {grade}, 캐릭터: {unit?.Name}");
}
// 확률 변동이 없는 가중치 확률
private void ItemSelect(int number)
{
if (_gradeRandom.GetList() == null) RandomInit(_prob);
for(int i = 0; i < number; i++)
{
ReturnData();
}
}
// 천장이 있는 가중치 확률
private void ItemSelectBySub(int number)
{
if (_gradeRandom.GetList() == null) RandomInit(_prob);
for(int i = 0; i < number; i++)
{
ReturnDataBySub();
}
}
}
아직은 Debug.Log로만 찍는 형태이지만 이후에 데이터베이스 연동 및 캐릭터 획득 여부에 대한 판정을 어떻게 할 것인지에 대한 데이터 반환 및 데이터베이스에 기록하는 등의 과정이 추가되어야 할 것이다.
시너지 시스템을 구성한 건 플레이어 데이터를 담당하고 있는 팀장님이다. 캐릭터를 소환함에 따라 시너지의 효과를 활성화하고(UI만) 정렬하는 기능까지 구현해 둔 상태였기에, 나 또한 로비에서의 편성 창에서 시너지 효과를 활성화하고 정렬하는 작업을 진행하려고 했다.
하지만 UnitData 자체가 워낙 방대한 데이터이기도 했지만 이거 구조를 분석하다 보니까 머리가 터질 것만 같았다. 그래서 간신히 구조를 분석해서 카드 자체에 UI를 띄우는 부분까지는 구현했지만, 시너지 효과 활성화 및 정렬 시스템은 당장 구현하기 어려워 보였다.
이 부분에 대해서는 팀장님의 도움을 받아서 같이 데이터를 분석하고 UI를 구현해보기로 했다.