Sous Raccoon
Perk and Shop Merchant
UI and System
System
ShopMerchantManager.cs
ShopMerchantManager.cs
UI
UIShopMerchantPanel.cs
UIShopMerchantItemSlot.cs
Sous Raccoon
Manager in Project
System
Sous Raccoon
Customer System
UI and System


using SousRaccoon.Manager; using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; public class SceneManagement : MonoBehaviour { public GameObject loadingCanvas; public Image loadBar; private void Start() { GameManager.instance.sceneManagement = this; } public void SelectState(string stateName) { Time.timeScale = 1.0f; //TODO : Loading Screen OnLoadingScreen($"State 0{stateName}"); } public void SelectTestState() { Time.timeScale = 1.0f; OnLoadingScreen("ForMidterm"); } public void SelectTargetScene(string stateName) { Time.timeScale = 1.0f; OnLoadingScreen(stateName); } public void StartNewTargetLevel(int levelIndex) { Time.timeScale = 1.0f; RunStageManager.instance.StartNewRunStage(); OnLoadingScreen(GameManager.instance.GetSceneLevel(levelIndex)); } public void StartSceneTutorial() { Time.timeScale = 1.0f; RunStageManager.instance.StartNewRunStage(); OnLoadingScreen("Tutorial"); } public void ReturnToLobby() { Time.timeScale = 1.0f; Cursor.visible = true; Cursor.lockState = CursorLockMode.None; OnLoadingScreen("Lobby"); } public void NextWinStage(string sceneName) { Time.timeScale = 1.0f; SceneManager.LoadScene(sceneName); } public void RestartScene() { Time.timeScale = 1.0f; RunStageManager.instance.StartNewRunStage(); OnLoadingScreen(SceneManager.GetActiveScene().name); } public void NextWinStageTest(string sceneName) { Time.timeScale = 1.0f; RunStageManager.instance.StartNewRunStage(); RunStageManager.instance.daysCount = 5; OnLoadingScreen(sceneName); } public void QuitGame() { Application.Quit(); } public void OnLoadingScreen(string sceneName) { if (loadingCanvas == null) SceneManager.LoadScene(sceneName); else StartCoroutine(LoadingScreenTimer(sceneName)); } public IEnumerator LoadingScreenTimer(string sceneName) { if (StageManager.instance != null) { StageManager.instance.canPause = false; } if (LobbyManager.instance != null) { LobbyManager.instance.canPause = false; } AudioManager.instance.StopMusic(); loadBar.fillAmount = 0; loadingCanvas.SetActive(true); Time.timeScale = 1; AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName); asyncOperation.allowSceneActivation = false; float progress = 0; while (!asyncOperation.isDone) { progress = Mathf.MoveTowards(progress, asyncOperation.progress, Time.deltaTime); loadBar.fillAmount = progress; if (progress >= 0.9f) { loadBar.fillAmount = 1; asyncOperation.allowSceneActivation = true; } yield return null
using SousRaccoon.Data; using SousRaccoon.Data.Item; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; namespace SousRaccoon.Manager { public class GameManager : MonoBehaviour { #region Singleton public static GameManager instance { get { return _instance; } } private static GameManager _instance; private void Awake() { // if the singleton hasn't been initialized yet if (_instance != null && _instance != this) { Destroy(this.gameObject); return;//Avoid doing anything else } if (_instance == null) { _instance = this; DontDestroyOnLoad(gameObject); } if (isDebug) Testing(); } #endregion public event Action OnChangeLanguageEvent; public SceneManagement sceneManagement; [Header("Player Data")] public PlayerDataBase playerDataBase; public PlayerSaveData playerSaveData; [Header("Asset Data")] public SpriteResourceSO spriteResource; public GameObject playerPrefab; public int currentSaveSlot; [Header("Stage List")] public StageListDataSO stageListData; public int currentLevelIndex; //Use For Check Level Index public List<AllItemDataEachList> allItemDataEachStage; public void ClearAllData() { PlayerPrefs.DeleteAll(); } #region Currency public int PlayerMoney { get; private set; } public bool isDebug; public void Testing() { PlayerSaveData saveData = new PlayerSaveData { LevelSpeed = 0, LevelRollCooldown = 0, LevelCombat = 0, LevelHeal = 0, LevelCustomer = 0, Skin = 0, MoneyCurrency = 0, }; playerSaveData = saveData; } public string GetSceneDebugStage(int levelIndex) { currentLevelIndex = levelIndex - 1; return stageListData.stageLists[1].sceneNameList[levelIndex - 1]; } public string GetSceneLevel(int levelIndex) { currentLevelIndex = levelIndex; return stageListData.stageLists[levelIndex].sceneNameList[0]; } public void SetCurrency() { PlayerMoney = PlayerPrefs.GetInt($"PLAYER_MONEY_CURRENCY_SAVE_{currentSaveSlot}"); SaveMoney(); } public void AddMoney(int money) { PlayerMoney += money; SaveMoney(); } public void RemoveMoney(int money) { PlayerMoney -= money; SaveMoney(); } private void SaveMoney() { playerSaveData.MoneyCurrency = PlayerMoney; PlayerPrefs.SetInt($"PLAYER_MONEY_CURRENCY_SAVE_{currentSaveSlot}", PlayerMoney); } public void SaveUnlockStage(int stageUnlockIndex) { playerSaveData.StageUnlock = stageUnlockIndex; PlayerPrefs.SetInt($"PLAYER_STATE_UNLOCK_SAVE_{currentSaveSlot}", stageUnlockIndex); } public void SaveRunComplete(int runCompleteIndex) { playerSaveData.RunComplete += runCompleteIndex; PlayerPrefs.SetInt($"RUN_COMPLETE_{currentSaveSlot}", playerSaveData.RunComplete); } #endregion public void GetRandomMap(int stageIndex, out string oldMapName, out int oldMapIndex, out string newMapName, out int newMapIndex) { // กำหนดค่า oldMapName ก่อน string currentStage = SceneManager.GetActiveScene().name; oldMapName = currentStage; // หา Index ของ Stage ปัจจุบันใน List oldMapIndex = stageListData.stageLists[stageIndex].sceneNameList.IndexOf(oldMapName); // กรองเฉพาะ Stage ที่ไม่ใช่ Stage ปัจจุบัน List<string> availableStages = stageListData.stageLists[stageIndex].sceneNameList .Where(stage => stage != currentStage) // ใช้ตัวแปรแทน oldMapName .ToList(); // เช็คว่ามี Stage ให้สุ่มหรือไม่ if (availableStages.Count == 0) { Debug.LogWarning("ไม่มี Stage ให้สุ่ม!"); newMapName = null; newMapIndex = -1; // ใช้ -1 เพื่อบอกว่าไม่มีด่านให้สุ่ม return; } // สุ่ม Stage จาก List ที่เหลือ newMapName = availableStages[UnityEngine.Random.Range(0, availableStages.Count)]; // หา Index ของ Stage ที่สุ่มได้ newMapIndex = stageListData.stageLists[stageIndex].sceneNameList.IndexOf(newMapName); } public void OnUpdateLanguage() { OnChangeLanguageEvent?.Invoke(); } public void AddOrUpdateEvent(EventTrigger trigger, EventTriggerType type, System.Action action) { var entry = trigger.triggers.FirstOrDefault(e => e.eventID == type); if (entry == null) { entry = new EventTrigger.Entry { eventID = type }; trigger.triggers.Add(entry); } // ป้องกันการเพิ่ม Callback ซ้ำ (เลือกใช้ถ้าจำเป็น) if (!entry.callback.GetPersistentEventCount().Equals(0)) { for (int i = 0; i < entry.callback.GetPersistentEventCount(); i++) { var methodName = entry.callback.GetPersistentMethodName(i); if (methodName == action.Method.Name) return; // มีอยู่แล้ว } } entry.callback.AddListener((data) => action.Invoke()); } public void ChangeUISelectNav(GameObject newButtonUI) { // ล้าง Selection ก่อน เพื่อป้องกันบั๊กบาง UI ไม่ยอมเปลี่ยน EventSystem.current.SetSelectedGameObject(null); // เลือก UI ตัวแรก EventSystem.current.SetSelectedGameObject(newButtonUI
using SousRaccoon.Data; using SousRaccoon.Kitchen; using SousRaccoon.Player; using SousRaccoon.UI.ShopMerchant; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace SousRaccoon.Manager { public class ShopMerchantManager : MonoBehaviour { [SerializeField] private GameObject merchantPrefab; [SerializeField] private Transform merchantSpawnPoint; [SerializeField] private float merchantLookAtTime; [Header("UI")] [SerializeField] private UIShopMerchantPanel merchantPanel; [SerializeField] private GameObject contentShop; public ShopMerchantItemDataBase coinExchangePerk; // เปลี่ยนให้ Action รับ int เพิ่ม private Dictionary<StatType, Action<List<StatModifier>>> statUpdateActions; public BarricadeManager barricadeManager; public HelperGiverAI helperGiverAIPref; // Assign By Prefab public Transform helperSpawnPoint; public Transform helperTrashPoint; public HelperGiverAI currentHelperGiver; public bool isShopOpen; public bool hasMoveIGD; [HideInInspector] public bool hasPerkSetup = false; private void Start() { barricadeManager = FindAnyObjectByType<BarricadeManager>(); // Initialize the dictionary with stat update methods statUpdateActions = new Dictionary<StatType, Action<List<StatModifier>>>() { { StatType.MoveSpeed, ApplyMoveSpeed }, { StatType.RollCDR, ApplyRollCoolDown }, { StatType.RollRange, ApplyRollRange }, { StatType.ATKSpeed, ApplyAttackSpeed }, { StatType.ATKDamage, ApplyDamage }, { StatType.Stun, ApplyStunRate }, { StatType.HealRate, ApplyHealRate }, { StatType.HealRange, ApplyHealRange }, { StatType.TwoHand, ApplyTwoHand }, { StatType.CustomerMoneyDrop, ApplyCustomerMoneyDrop }, { StatType.CoinSpwnRate, ApplyCoinSpawnRate }, { StatType.CookSpeedChef, ApplySpeedChef }, { StatType.AngryLimitChef, ApplyAngryLimitChef }, { StatType.Money, ApplyMoney }, { StatType.BarricadeStat, ApplyBarricadeStat }, { StatType.HelperGiverStat, ApplyHelperGiverStat }, { StatType.CoinExchange, ApplyCoinExchange}, }; StartCoroutine(WaitAllThingSet()); } private IEnumerator WaitAllThingSet() { yield return new WaitUntil(() => FindObjectOfType<PlayerCombatSystem>() != null && FindObjectOfType<PlayerLocomotion>() != null && FindObjectOfType<SpawnerManager>() != null && FindObjectOfType<ChefAI>() != null && FindObjectOfType<StageScenarioManager>().scenarioSet || !FindObjectOfType<StageScenarioManager>().hasSceneario); LoadStatRunStage
using System.Collections.Generic; using UnityEngine; using UnityEngine.Localization; namespace SousRaccoon.Data { public enum TypeOfPerk { Status, Currency, } [CreateAssetMenu(fileName = "ShopMerchantItemData", menuName = "GameData/ShopMerchantItemData")] public class ShopMerchantItemDataBase : ScriptableObject { public string perkName; public LocalizedString perkDisplayName; public LocalizedString description; public Sprite icon; [Space(35)] public TypeOfPerk typeOfPerk; [Space(20)] public List<int> levelPrices; public List<PerkStatModifier> statModifiers; } [System.Serializable] public class PerkStatModifier { public List<StatModifier> modifiers; // รายการของค่าที่จะเปลี่ยน } [System.Serializable] public class StatModifier { public StatType stat; // ประเภทของ Stat เช่น Health, Damage, Speed, IsInvincible public float floatValue; // ค่าแบบ Float public int intValue; // ค่าแบบ Int public bool boolValue; // ค่าแบบ Bool } public enum StatType { MoveSpeed, RollCDR, RollRange, ATKSpeed, ATKDamage, Stun, HealRate, HealRange, TwoHand, CustomerMoneyDrop, CoinSpwnRate, CookSpeedChef, AngryLimitChef, Money, BarricadeStat, HelperGiverStat, CoinExchange
using SousRaccoon.Data; using SousRaccoon.Manager; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI; namespace SousRaccoon.UI.ShopMerchant { public class UIShopMerchantPanel : MonoBehaviour { public Button rerollButton; public TMP_Text rerollText; public int reRollTimes; public TMP_Text playerCoinText; public UIShopMerchantItemSlot baseSlot; public Transform perkLayout; public ShopMerchantManager shopMerchantManager; public List<ShopMerchantItemDataBase> perkSpawnList = new List<ShopMerchantItemDataBase>(); //public List<ShopMerchantItemDataBase> perkCurrentList = new List<ShopMerchantItemDataBase>(); public List<UIShopMerchantItemSlot> uIShopMerchantItemSlots = new List<UIShopMerchantItemSlot>(); private void OnDestroy() { if (rerollButton != null) rerollButton.onClick.RemoveListener(RerollPerk); } public void SetUpShopUI() { rerollText.text = reRollTimes.ToString(); rerollButton.onClick.AddListener(RerollPerk); playerCoinText.text = RunStageManager.instance.PlayerCoin.ToString(); RandomPerk(); } public void SetUpLastDayShopUI() { rerollButton.gameObject.SetActive(false); playerCoinText.text = RunStageManager.instance.PlayerCoin.ToString(); CoinExchangePerk(); } public void RandomPerk() { perkSpawnList = RunStageManager.instance.RandomPerks(); foreach (var perk in perkSpawnList) { var perkCurrent = Instantiate(baseSlot, perkLayout); var perkLevel = RunStageManager.instance.GetPerkLevel(perk.perkName); var canBuy = shopMerchantManager.CheckPlayerCanBuy(perk.levelPrices[perkLevel]); perkCurrent.slotData = perk; perkCurrent.SetUpSlot(perk.icon, perkLevel + 1, perk.perkDisplayName, perk.description, perk.levelPrices[perkLevel], canBuy, shopMerchantManager); //perkCurrentList.Add(perk); uIShopMerchantItemSlots.Add(perkCurrent); } } public void CoinExchangePerk() { var perkCurrent = Instantiate(baseSlot, perkLayout); perkCurrent.slotData = shopMerchantManager.coinExchangePerk; var perkPrice = RunStageManager.instance.PlayerCoin; perkCurrent.SetUpSlot(shopMerchantManager.coinExchangePerk.icon, 0, shopMerchantManager.coinExchangePerk.perkDisplayName, shopMerchantManager.coinExchangePerk.description, perkPrice, true, shopMerchantManager); //perkCurrentList.Add(shopMerchantManager.coinExchangePerk); uIShopMerchantItemSlots.Add(perkCurrent); } public void UpdateSlot() { foreach (var perkSlot in uIShopMerchantItemSlots) { if (!perkSlot.isSoldOut) { var perkLevel = RunStageManager.instance.GetPerkLevel(perkSlot.slotData.perkName); var canBuy = shopMerchantManager.CheckPlayerCanBuy(perkSlot.slotData.levelPrices[perkLevel]); perkSlot.OnUpdateSlot(canBuy); } } playerCoinText.text = RunStageManager.instance.PlayerCoin.ToString(); } public void RerollPerk() { if (reRollTimes <= 0) return; reRollTimes--; rerollText.text = reRollTimes.ToString(); foreach (var perk in uIShopMerchantItemSlots) { Destroy(perk.gameObject); } perkSpawnList.Clear(); uIShopMerchantItemSlots.Clear(); RandomPerk
using SousRaccoon.Data; using SousRaccoon.Manager; using System; using TMPro; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Components; using UnityEngine.UI; namespace SousRaccoon.UI.ShopMerchant { public class UIShopMerchantItemSlot : MonoBehaviour { public event Action<ShopMerchantItemDataBase> EventOnBuyButtonClicked; [SerializeField] private Button buyButton; [SerializeField] private Image slotIcon; [SerializeField] private TMP_Text levelText; [SerializeField] private LocalizeStringEvent nameText; [SerializeField] private LocalizeStringEvent discriptionText; [SerializeField] private TMP_Text priceText; [SerializeField] private GameObject soldOutPanel; [SerializeField] private GameObject cantBuyButton; public ShopMerchantManager shopMerchantManager; public ShopMerchantItemDataBase slotData; public bool isSoldOut = false; protected void Awake() { buyButton.onClick.AddListener(OnBuyButtonClicked); } protected void OnDestroy() { buyButton.onClick.RemoveAllListeners(); EventOnBuyButtonClicked -= shopMerchantManager.OnBuyingPerk; } public void SetUpSlot(Sprite icon, int levelOfPerk, LocalizedString nameOfPerk, LocalizedString discriptionOfPerk, int priceOfPerk, bool canBuy, ShopMerchantManager shopManager) { shopMerchantManager = shopManager; EventOnBuyButtonClicked += shopMerchantManager.OnBuyingPerk; slotIcon.sprite = icon; levelText.text = levelOfPerk.ToString(); nameText.StringReference = nameOfPerk; discriptionText.StringReference = discriptionOfPerk; priceText.text = $"{priceOfPerk}"; cantBuyButton.SetActive(!canBuy); buyButton.interactable = canBuy; } public void OnUpdateSlot(bool canBuy) { cantBuyButton.SetActive(!canBuy); buyButton.interactable = canBuy; } private void OnBuyButtonClicked() { Debug.LogError("Buy"); AudioManager.instance.PlayStageSFXOneShot("CashMoney"); soldOutPanel.SetActive(true); buyButton.interactable = false; isSoldOut = true; EventOnBuyButtonClicked?.Invoke(slotData
using SousRaccoon.Kitchen; using SousRaccoon.Manager; using System.Collections; using TMPro; using UnityEngine; using UnityEngine.AI; using static SousRaccoon.Customer.CustomerStatus; namespace SousRaccoon.Customer { public class CustomerMovement : MonoBehaviour { public const float THINK_TIME_MIN = 2; public const float THINK_TIME_MAX = 4; public const float EATING_TIME = 7.5f; NavMeshAgent agent; StageManager stateManager; CustomerStatus status; Animator animator; Vector3 targetDestination; [SerializeField] public ActionState state; [SerializeField] float moveSpeed; [SerializeField] float hurtBuffSpeed; [SerializeField] float rotationSpeed; [SerializeField] float lockDistance; [SerializeField] float hurtBuffDuration; private Coroutine currentHurtBuffCoroutine; [SerializeField] Chair currentChair; [SerializeField] Table currentTable; private static object chairLock = new object(); [SerializeField] GameObject eatProp; [SerializeField] GameObject orderProp; [SerializeField] Animator orderAnim; [SerializeField] bool fixSittingAnim; [SerializeField] float newTimeWaiting; [Header("Money")] [SerializeField] GameObject moneyDropUI; [SerializeField] TMP_Text moneyText; [HideInInspector] public bool isOverThinking = false; bool isSit = false; //Cheack Chair For Trash public enum ActionState { Idle, Walk, Sit, Menu, Wait, Eat, Getup, Die, Upset, } // Start is called before the first frame update void Start() { agent = GetComponent<NavMeshAgent>(); status = GetComponent<CustomerStatus>(); animator = GetComponentInChildren<Animator>(); stateManager = FindAnyObjectByType<StageManager>(); agent.speed = moveSpeed; // ในการจองเก้าอี้ใน Start() lock (chairLock) { currentChair = stateManager.GetAvailableChair(out var table); if (currentChair == null || stateManager.currentCustomerDieCount > 0) StartIdle(); else { currentTable = table; currentTable.OccupyChair(currentChair); // จองเก้าอี้ StartWalking(currentChair.walkInPositon.position); } } status.EnterUIState(UIState.normal); } // Update is called once per frame void Update() { if (state == ActionState.Walk) { RotateTowardsTarget(); } switch (state) { case ActionState.Idle: HandleIdle(); break; case ActionState.Walk: HandleWalking(); break; case ActionState.Sit: HandleSit(); break; case ActionState.Menu: HandleMenu(); break; case ActionState.Wait: HandleWait(); break; case ActionState.Eat: HandleEat(); break; case ActionState.Getup: HandleGetUp(); break; case ActionState.Die: break; case ActionState.Upset: HandleUpset(); break


using SousRaccoon.Data; using SousRaccoon.Manager; using System.Collections; using TMPro; using UnityEngine; using UnityEngine.UI; using UnityEngine.VFX; namespace SousRaccoon.Customer { public class CustomerStatus : MonoBehaviour { [HideInInspector] public CustomerMovement movement; [HideInInspector] public bool canGiveOrder = false; [Header("Time")] // TODO : maxHealthTime เดี๋ยวจะไปดึงมาจาก Player Upgrade // Health Time public float maxHealthTime; public float currentHealthTime; public float countDownPerSecond = 1f; // damage dealt per second public float countDownPlusWaiting = 0.25f; // damage dealt per second [Header("Perk")] public int blockTimes = 0; public int maxMoneyDropRunStage; public int midMoneyDropRunStage; public int minMoneyDropRunStage; [SerializeField] int maxMoneyDrop; [SerializeField] int midMoneyDrop; [SerializeField] int minMoneyDrop; [Header("Take Damage")] [SerializeField] protected VisualEffect takeDamageVFX; [SerializeField] protected SkinnedMeshRenderer baseMeshRenderer; [SerializeField] protected Material baseMat; [SerializeField] protected Material takeDamageMat; [SerializeField] protected float matDuration; Coroutine takeDamageCoroutine; [Header("Heal")] public Animator healAnim; [Header("Condition")] public bool isCountDownOverTime = false; // flag to check if health is decreasing over time public bool isDead = false; public bool isUpset = false; public bool canHeal = true; public bool isTutorial = false; public bool isWaiting = false; GameObject currentPanel; public int htState; [SerializeField] Sprite greenBar; [SerializeField] Sprite yellowBar; [SerializeField] Sprite redBar; [SerializeField] Sprite purpleBar; [SerializeField] GameObject htBarPanel; [SerializeField] Image htBar; // health bar image [Header("Order")] [SerializeField] GameObject orderPanel; // Panel [SerializeField] Image orderOrderIcon; // Order Icon in order State [SerializeField] GameObject menuIcon; [SerializeField] GameObject waterMenuIcon; [Header("Wait")] [SerializeField] GameObject waitPanel; // Panel [SerializeField] Image waitOrderIcon; // Order Icon in wait State [Header("Upset")] [SerializeField] GameObject AngryVFX; [Header("Dead")] public GameObject deadPref; [Header("Debug")] //TODO : Delete it. This is for Debug public TMP_Text htBarText; // health bar text [SerializeField] public UIState currentState; public enum UIState { normal, thinking, order, wait, dead, getup, upset, } // Start is called before the first frame update void Start() { LoadPlayerStatus(); movement = GetComponent<CustomerMovement>(); currentPanel = null; canHeal = true; UpdateHealthBar(); // Initialize health bar on start } // Update is called once per frame void Update() { if (isDead || isUpset) return; if (isCountDownOverTime && !isTutorial) { HealthTimeCount(); UpdateHealthBar
CustomerStatus.cs
