ui 체크리스트 기능 추가

This commit is contained in:
NTG 2025-08-17 21:51:51 +09:00
parent 4b1aa5229a
commit ff66c5d94f
4 changed files with 254 additions and 71 deletions

View File

@ -0,0 +1,29 @@
using TMPro;
using UnityEngine;
using UnityEngine.Localization.Components;
using UnityEngine.UI;
namespace DDD
{
public class ChecklistData : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI _textLabel;
[SerializeField] private Image _checkLineImage;
public void Initialize()
{
_textLabel.text = string.Empty;
_checkLineImage.gameObject.SetActive(false);
}
public void UpdateData(string localizationKey, bool isChecked)
{
SetText(localizationKey);
SetActiveCheckLine(isChecked);
}
public void SetText(string localizationKey) => _textLabel.text = LocalizationManager.Instance.GetString(localizationKey);
public void SetActiveCheckLine(bool isActive) => _checkLineImage.gameObject.SetActive(isActive);
public bool IsChecked() => _checkLineImage.gameObject.activeSelf;
}
}

View File

@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Localization.Components;
using UnityEngine.UI;
namespace DDD
{
public enum ChecklistLocalizationKey
{
Checklist1 = 0,
Checklist2,
Checklist3,
}
public class ChecklistView : MonoBehaviour, IEventHandler<TodayMenuAddedEvent>, IEventHandler<TodayMenuRemovedEvent>
{
private List<ChecklistData> _checklistDatas;
private Dictionary<ChecklistLocalizationKey, string> _checklistLocalizationKeys = new()
{
{ChecklistLocalizationKey.Checklist1, "checklist_1"},
{ChecklistLocalizationKey.Checklist2, "checklist_2"},
{ChecklistLocalizationKey.Checklist3, "checklist_3"},
};
private RestaurantManagementStateSo restaurantManagementStateSo;
public void Initalize()
{
restaurantManagementStateSo = RestaurantState.instance.ManagementState;
_checklistDatas = new List<ChecklistData>(3);
_checklistDatas = GetComponentsInChildren<ChecklistData>().ToList();
foreach (var checklistData in _checklistDatas)
{
checklistData.Initialize();
}
UpdateView();
EventBus.Register<TodayMenuAddedEvent>(this);
EventBus.Register<TodayMenuRemovedEvent>(this);
}
public void UpdateView()
{
if (restaurantManagementStateSo == null) return;
bool[] states = restaurantManagementStateSo.GetChecklistStates();
int loopCount = Mathf.Min(_checklistDatas.Count, states.Length);
for (int i = 0; i < loopCount; i++)
{
_checklistDatas[i].UpdateData(_checklistLocalizationKeys[(ChecklistLocalizationKey)i], states[i]);
}
}
public void Invoke(TodayMenuRemovedEvent evt) => UpdateView();
public void Invoke(TodayMenuAddedEvent evt) => UpdateView();
private void OnDestroy()
{
EventBus.Unregister<TodayMenuAddedEvent>(this);
EventBus.Unregister<TodayMenuRemovedEvent>(this);
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
@ -9,6 +10,7 @@ namespace DDD
{
public class RestaurantManagementUi : PopupUi<RestaurantUiActions>, IEventHandler<TodayMenuRemovedEvent>
{
[SerializeField] private ChecklistView _checklistView;
[SerializeField] private InventoryView _inventoryView;
[SerializeField] private ItemDetailView _itemDetailView;
[SerializeField] private TabGroupUi _sectionTabs;
@ -16,51 +18,66 @@ public class RestaurantManagementUi : PopupUi<RestaurantUiActions>, IEventHandle
[SerializeField] private TabGroupUi _cookwareCategoryTabs;
[SerializeField] private Image _completeBatchFilledImage;
[SerializeField] private float _holdCompleteTime = 1f;
private float _elapsedTime;
private bool _isHolding;
private const string ChecklistFailedMessageKey = "checklist_failed_message";
protected override void Update()
{
base.Update();
if (_isHolding)
{
if (_holdCompleteTime <= 0f)
{
_elapsedTime = 0f;
HandleInteract2Canceled();
var evt = GameEvents.OpenPopupUiEvent;
evt.UiType = typeof(ConfirmUi);
evt.IsCancelButtonVisible = true;
evt.NewMessageKey = "Global_Message_001";
evt.OnConfirm = ClosePanel;
EventBus.Broadcast(evt);
return;
}
_completeBatchFilledImage.fillAmount = _elapsedTime;
var multiply = 1f / _holdCompleteTime;
if (_elapsedTime >= 1f)
{
// TODO : 추후에 체크리스트와 비교해서 팝업 띄울지 말지 결정
HandleInteract2Canceled();
var evt = GameEvents.OpenPopupUiEvent;
evt.UiType = typeof(ConfirmUi);
evt.IsCancelButtonVisible = true;
evt.NewMessageKey = "Global_Message_001";
evt.OnConfirm = ClosePanel;
EventBus.Broadcast(evt);
return;
}
_elapsedTime += Time.deltaTime * multiply;
UpdateHoldProgress();
}
}
private void UpdateHoldProgress()
{
if (_holdCompleteTime <= 0f)
{
HandleInteract2Canceled();
ProcessCompleteBatchAction();
return;
}
_completeBatchFilledImage.fillAmount = _elapsedTime;
var multiply = 1f / _holdCompleteTime;
if (_elapsedTime >= 1f)
{
HandleInteract2Canceled();
ProcessCompleteBatchAction();
return;
}
_elapsedTime += Time.deltaTime * multiply;
}
private void ProcessCompleteBatchAction()
{
if (RestaurantState.instance.ManagementState.GetChecklistStates().Any(state => state == false))
{
ShowChecklistFailedPopup();
}
else
{
Close();
}
}
private void ShowChecklistFailedPopup()
{
var evt = GameEvents.OpenPopupUiEvent;
evt.UiType = typeof(ConfirmUi);
evt.IsCancelButtonVisible = true;
evt.NewMessageKey = ChecklistFailedMessageKey;
evt.OnConfirm = ClosePanel;
EventBus.Broadcast(evt);
}
protected override GameObject GetInitialSelected()
{
var inventoryViewInitialSelectedObject = _inventoryView.GetInitialSelected();
@ -84,19 +101,18 @@ protected override GameObject GetInitialSelected()
public override void Open(OpenPopupUiEvent evt)
{
base.Open(evt);
_inventoryView.Initialize();
// 각 그룹별로 허용된 카테고리 설정
InitializeViews();
SetupCategoryTabs();
_sectionTabs.Initialize(OnSectionTabSelected);
_menuCategoryTabs.Initialize(OnCategoryTabSelected);
_cookwareCategoryTabs.Initialize(OnCategoryTabSelected);
_sectionTabs.SelectFirstTab();
_menuCategoryTabs.SelectFirstTab();
EventBus.Register<TodayMenuRemovedEvent>(this);
InitializeTabGroups();
SelectInitialTabs();
RegisterEventHandlers();
}
private void InitializeViews()
{
_checklistView.Initalize();
_inventoryView.Initialize();
}
/// <summary>
@ -109,17 +125,35 @@ private void SetupCategoryTabs()
_cookwareCategoryTabs.UseDefaultAllowedValues();
}
private void InitializeTabGroups()
{
_sectionTabs.Initialize(OnSectionTabSelected);
_menuCategoryTabs.Initialize(OnCategoryTabSelected);
_cookwareCategoryTabs.Initialize(OnCategoryTabSelected);
}
private void SelectInitialTabs()
{
_sectionTabs.SelectFirstTab();
_menuCategoryTabs.SelectFirstTab();
}
private void RegisterEventHandlers()
{
EventBus.Register<TodayMenuRemovedEvent>(this);
}
public override void Close()
{
base.Close();
EventBus.Unregister<TodayMenuRemovedEvent>(this);
}
protected override bool OnInputPerformed(RestaurantUiActions actionEnum, InputAction.CallbackContext context)
{
if (base.OnInputPerformed(actionEnum, context) == false) return false;
switch (actionEnum)
{
case RestaurantUiActions.Cancel:
@ -141,11 +175,11 @@ protected override bool OnInputPerformed(RestaurantUiActions actionEnum, InputAc
return true;
}
protected override bool OnInputCanceled(RestaurantUiActions actionEnum, InputAction.CallbackContext context)
{
if (base.OnInputPerformed(actionEnum, context) == false) return false;
switch (actionEnum)
{
case RestaurantUiActions.Interact2:
@ -172,7 +206,7 @@ private void HandleInteract1Performed()
var interactable = selected?.GetComponent<IInteractableUi>();
interactable?.OnInteract();
}
private void HandleInteract2Performed()
{
_isHolding = true;
@ -207,7 +241,7 @@ private void OnCategoryTabSelected(int categoryValue)
_inventoryView.UpdateCategoryView(category);
_itemDetailView.UpdateCategory(category);
}
public void Invoke(TodayMenuRemovedEvent evt)
{
_menuCategoryTabs.SelectTab((int)evt.InventoryCategoryType);

View File

@ -8,26 +8,36 @@ namespace DDD
{
public class RestaurantManagementStateSo : ScriptableObject
{
// TODO : 체크리스트 기능
// TODO : 데이터에서 초기화하고, 동적으로 변경
[Title("오늘의 레스토랑 상태")]
public int MaxFoodCount = 8;
public int MaxDrinkCount = 6;
public int MaxCookwareCount = 6;
[Title("체크리스트 조건")]
public int ChecklistFoodCount = 1;
public int ChecklistCookwareCount = 1;
public int ChecklistMatchedMenuWithCookwareCount = 1;
[Title("실시간 데이터")]
[ReadOnly, SerializeField] private bool _isOpenable;
[ReadOnly, ShowInInspector] private Dictionary<string, int> _todayFoodRecipeIds = new();
[ReadOnly, ShowInInspector] private Dictionary<string, int> _todayDrinkRecipeIds = new();
[ReadOnly, ShowInInspector] private List<string> _todayWorkerIds = new();
[ReadOnly, ShowInInspector] private Dictionary<string, HashSet<string>> _cookwareToRecipeIds = new();
public IReadOnlyDictionary<string, int> TodayFoodRecipeIds => _todayFoodRecipeIds;
public IReadOnlyDictionary<string, int> TodayDrinkRecipeIds => _todayDrinkRecipeIds;
public IReadOnlyList<string> TodayWorkerIds => _todayWorkerIds;
public IReadOnlyDictionary<string, HashSet<string>> CookwareToRecipeIds => _cookwareToRecipeIds;
public int AddedTodayMenuCount => AddedTodayFoodCount + AddedTodayDrinkCount;
public int AddedTodayFoodCount => _todayFoodRecipeIds.Count;
public int AddedTodayDrinkCount => _todayDrinkRecipeIds.Count;
public int AddedTodayWorkerCount => _todayWorkerIds.Count;
public int AddedTodayCookwareCount => _cookwareToRecipeIds.Count;
public int MatchedTodayMenuWithCookwareCount => _cookwareToRecipeIds.Values.Count(recipeSet => recipeSet.Count > 0);
public void InitializeReadyForRestaurant()
{
// TODO : Load from disk if possible (save data)
@ -37,6 +47,34 @@ public void InitializeReadyForRestaurant()
_cookwareToRecipeIds.Clear();
}
/// <summary>
/// 체크리스트 항목들의 상태를 확인하는 메서드
/// </summary>
/// <returns>체크리스트 완료 여부 배열 (순서: 음식레시피, 조리도구, 매칭여부)</returns>
public bool[] GetChecklistStates()
{
bool hasFood = HasFoodRecipes();
bool hasCookware = HasCookwares();
bool hasMatching = HasMatchedMenuWithCookware();
return new bool[] { hasFood, hasCookware, hasMatching };
}
public bool HasFoodRecipes()
{
return _todayFoodRecipeIds.Count > 0;
}
public bool HasCookwares()
{
return _cookwareToRecipeIds.Count > 0;
}
public bool HasMatchedMenuWithCookware()
{
return _cookwareToRecipeIds.Values.Any(recipeSet => recipeSet.Count > 0);
}
public bool IsOpenable()
{
// TODO : 영업 가능한 상태인지 조건 추가 (최소 요리, 요리도구 배치 등)
@ -55,13 +93,13 @@ public RestaurantManagementDataSo GetManagementData()
public bool TryAddTodayMenu(ItemViewModel model)
{
string recipeId = model.Id;
if (model.ItemType != ItemType.Recipe) return false;
if (DataManager.Instance.GetDataSo<RecipeDataSo>().TryGetDataById(recipeId, out RecipeData recipeData) == false) return false;
bool added = false;
if (recipeData.RecipeType == RecipeType.FoodRecipe)
{
if (_todayFoodRecipeIds.Count >= MaxFoodCount || _todayFoodRecipeIds.ContainsKey(recipeId)) return false;
@ -92,7 +130,10 @@ public bool TryAddTodayMenu(ItemViewModel model)
{
recipeSet.Add(recipeId);
}
var dirtyEvt = GameEvents.SmartVariablesDirtyEvent;
dirtyEvt.DomainFlags = SmartVariablesDomain.RestaurantToday;
EventBus.Broadcast(dirtyEvt);
EventBus.Broadcast(RestaurantEvents.TodayMenuAddedEvent);
}
@ -102,7 +143,7 @@ public bool TryAddTodayMenu(ItemViewModel model)
public bool TryRemoveTodayMenu(ItemViewModel model)
{
string recipeId = model.Id;
var evt = RestaurantEvents.TodayMenuRemovedEvent;
var removedEvt = RestaurantEvents.TodayMenuRemovedEvent;
if (DataManager.Instance.GetDataSo<RecipeDataSo>().TryGetDataById(recipeId, out RecipeData recipeData) == false) return false;
@ -114,7 +155,7 @@ public bool TryRemoveTodayMenu(ItemViewModel model)
if (_todayFoodRecipeIds.TryGetValue(recipeId, out refundCount))
{
removed = _todayFoodRecipeIds.Remove(recipeId);
evt.InventoryCategoryType = InventoryCategoryType.Food;
removedEvt.InventoryCategoryType = InventoryCategoryType.Food;
if (removed)
{
@ -128,7 +169,7 @@ public bool TryRemoveTodayMenu(ItemViewModel model)
if (_todayDrinkRecipeIds.TryGetValue(recipeId, out refundCount))
{
removed = _todayDrinkRecipeIds.Remove(recipeId);
evt.InventoryCategoryType = InventoryCategoryType.Drink;
removedEvt.InventoryCategoryType = InventoryCategoryType.Drink;
if (removed)
{
@ -146,7 +187,11 @@ public bool TryRemoveTodayMenu(ItemViewModel model)
recipeSet.Remove(recipeId);
}
EventBus.Broadcast(evt);
var dirtyEvt = GameEvents.SmartVariablesDirtyEvent;
dirtyEvt.DomainFlags = SmartVariablesDomain.RestaurantToday;
EventBus.Broadcast(dirtyEvt);
EventBus.Broadcast(removedEvt);
}
return removed;
@ -155,13 +200,13 @@ public bool TryRemoveTodayMenu(ItemViewModel model)
public bool TryAddTodayCookware(ItemViewModel model)
{
var cookwareId = model.Id;
if (model.HasItem == false || DataManager.Instance.GetDataSo<CookwareDataSo>().ContainsData(cookwareId) == false) return false;
if (_cookwareToRecipeIds.Count >= MaxCookwareCount || _cookwareToRecipeIds.ContainsKey(cookwareId)) return false;
_cookwareToRecipeIds[cookwareId] = new HashSet<string>();
foreach (var recipeId in _todayFoodRecipeIds.Keys.Concat(_todayDrinkRecipeIds.Keys))
{
var required = GetRequiredCookwareKey(recipeId);
@ -170,13 +215,16 @@ public bool TryAddTodayCookware(ItemViewModel model)
_cookwareToRecipeIds[cookwareId].Add(recipeId);
}
}
EventBus.Broadcast(RestaurantEvents.TodayMenuAddedEvent);
var dirtyEvt2 = GameEvents.SmartVariablesDirtyEvent;
dirtyEvt2.DomainFlags = SmartVariablesDomain.RestaurantToday;
EventBus.Broadcast(dirtyEvt2);
return true;
}
public bool IsContainTodayMenu(string recipeId)=> _todayFoodRecipeIds.ContainsKey(recipeId) || _todayDrinkRecipeIds.ContainsKey(recipeId);
public bool IsContainTodayMenu(string recipeId) => _todayFoodRecipeIds.ContainsKey(recipeId) || _todayDrinkRecipeIds.ContainsKey(recipeId);
public bool TryRemoveTodayCookware(ItemViewModel model)
{
@ -189,10 +237,13 @@ public bool TryRemoveTodayCookware(ItemViewModel model)
var evt = RestaurantEvents.TodayMenuRemovedEvent;
evt.InventoryCategoryType = InventoryCategoryType.Cookware;
EventBus.Broadcast(evt);
var dirtyEvt3 = GameEvents.SmartVariablesDirtyEvent;
dirtyEvt3.DomainFlags = SmartVariablesDomain.RestaurantToday;
EventBus.Broadcast(dirtyEvt3);
return true;
}
private string GetRequiredCookwareKey(string recipeId)
{
if (DataManager.Instance.GetDataSo<RecipeDataSo>().TryGetDataById(recipeId, out var recipeData) == false) return null;