Refactor course maps and travel UI

This commit is contained in:
jongjae0305
2026-07-07 15:57:11 +09:00
parent 74498dcf87
commit a708f18499
37 changed files with 1983 additions and 1633 deletions
+620 -33
View File
@@ -15,7 +15,8 @@ public class GameFlowManager : MonoBehaviour {
Game,
Result,
DutyFreeShop,
MapPreview
MapPreview,
DebugMenu
}
public static GameFlowManager Instance { get; private set; }
@@ -39,6 +40,27 @@ public class GameFlowManager : MonoBehaviour {
private const string ResultSceneName = "Result";
private const string DutyFreeShopSceneName = "DutyFreeShop";
private const string MapPreviewSceneName = "MapPreview";
private const string DepartureStampResourcePath = "UI_DepartureStamp_Red";
private const string ResultPassportPanelResourcePath = "UI_LeftHudPassportHorizontalGridPanel";
private const string ResultAirportBackgroundResourcePath = "Map_Tutorial_Incheon_Airport";
private const string ResultButtonResourcePath = "UI_TutorialNextButton";
private const string ResultButtonAssetPath = "Assets/Resources/UI_TutorialNextButton.png";
private const string MileageIconAssetPath = "Assets/Sprites/UIIcons/UI_Mileage.png";
private const string TimerIconAssetPath = "Assets/Sprites/UIIcons/UI_Timer.png";
private const string CheckpointIconAssetPath = "Assets/Sprites/UIIcons/UI_Checkpoint.png";
private const string ClearRankIconAssetPath = "Assets/Sprites/UIIcons/UI_ClearRank.png";
private const string DutyFreeIconAssetPath = "Assets/Sprites/UIIcons/UI_DutyFreeShop.png";
private const string AirplaneTravelIconAssetPath = "Assets/Sprites/UIIcons/UI_AirplaneTravel.png";
private const string DutyFreeBackgroundAssetPath = "Assets/ConceptArt/duty_free_shop_screen_concept_01.png";
private const string DutyFreeLunchboxAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_Lunchbox.png";
private const string DutyFreeShieldAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_ShieldCharm.png";
private const string DutyFreeShoesAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_Shoes.png";
private const string DutyFreeMileageCardAssetPath = "Assets/Sprites/DutyFree/DutyFree_Normal_MileageCard.png";
private const string SouvenirCollectionIconAssetPath = "Assets/Sprites/UIIcons/UI_SouvenirCollection.png";
private const string AirportSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_AirportMagnet.png";
private const string JapanSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_Japan_Magnet.png";
private const string ChinaSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_TempleCharm.png";
private const string AmericaSouvenirAssetPath = "Assets/Sprites/Souvenirs/Souvenir_SkylineMagnet.png";
private const int ShieldCost = 120;
private const int LunchboxCost = 80;
private const int SpeedShoesCost = 140;
@@ -98,6 +120,50 @@ public class GameFlowManager : MonoBehaviour {
ShowIntro();
}
#if UNITY_EDITOR || DEVELOPMENT_BUILD
private void Update() {
if(Input.GetKeyDown(KeyCode.Escape))
{
ShowDebugMenu();
return;
}
HandleDebugMenuShortcutInput();
}
private void HandleDebugMenuShortcutInput() {
if(currentScreen != FlowScreen.DebugMenu)
{
return;
}
if(Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1))
{
ShowDebugIntroPreview();
}
else if(Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2))
{
ShowDebugGameStartPreview();
}
else if(Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3))
{
ShowDebugResultPreview(false);
}
else if(Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4))
{
ShowDebugResultPreview(true);
}
else if(Input.GetKeyDown(KeyCode.Alpha5) || Input.GetKeyDown(KeyCode.Keypad5))
{
ShowDebugDutyFreeShopPreview();
}
else if(Input.GetKeyDown(KeyCode.Alpha6) || Input.GetKeyDown(KeyCode.Keypad6))
{
ShowDebugMapPreview();
}
}
#endif
private void OnDestroy() {
if(Instance == this)
{
@@ -173,6 +239,95 @@ public class GameFlowManager : MonoBehaviour {
ShowResult();
}
#if UNITY_EDITOR || DEVELOPMENT_BUILD
private void ShowDebugMenu() {
EnsureDebugPreviewData();
gameplayActive = false;
pendingStartGameplay = false;
currentScreen = FlowScreen.DebugMenu;
EnsureOverlay();
ClearOverlay();
SetOverlayVisible(true);
SetDefaultOverlayBackground();
AddTitle("디버그 메뉴");
AddBody("화면 조정용 바로 이동\n숫자키 1~6도 사용할 수 있습니다.");
AddButton("1. 인트로", ShowDebugIntroPreview);
AddButton("2. 게임 시작", ShowDebugGameStartPreview);
AddButton("3. 출국 실패", delegate { ShowDebugResultPreview(false); });
AddButton("4. 출국 성공", delegate { ShowDebugResultPreview(true); });
AddButton("5. 면세점", ShowDebugDutyFreeShopPreview);
AddButton("6. 지도", ShowDebugMapPreview);
}
private void ShowDebugIntroPreview() {
ShowIntro();
}
private void ShowDebugGameStartPreview() {
EnsureDebugPreviewData();
LoadGameSceneForCurrentMap();
}
private void ShowDebugResultPreview(bool cleared) {
EnsureDebugPreviewData();
gameplayActive = false;
pendingStartGameplay = false;
currentScreen = FlowScreen.Result;
completedMap = currentMap;
lastResultCleared = cleared;
lastResultScore = cleared ? 12800 : 7200;
lastResultMileage = cleared ? 263 : 84;
lastResultTime = cleared ? 23.9f : 38.4f;
lastResultDistance = completedMap != null
? (cleared ? completedMap.targetDistance : completedMap.targetDistance * 0.62f)
: 380f;
if(saveData != null)
{
saveData.totalMileage = Mathf.Max(saveData.totalMileage, 4120);
}
ShowResult();
}
private void ShowDebugDutyFreeShopPreview() {
EnsureDebugPreviewData();
if(saveData != null)
{
saveData.totalMileage = Mathf.Max(saveData.totalMileage, 4120);
}
ShowDutyFreeShop();
}
private void ShowDebugMapPreview() {
EnsureDebugPreviewData();
ShowMapPreview();
}
private void EnsureDebugPreviewData() {
if(saveData == null)
{
saveData = GameSaveManager.Load();
}
if(currentMap == null)
{
currentMap = MapDatabase.GetMapByIndex(saveData != null ? saveData.currentMapIndex : 0);
}
if(currentMap == null)
{
currentMap = MapDatabase.GetMapByIndex(0);
}
}
#endif
private void StartNewJourney() {
saveData = GameSaveManager.ResetSave();
currentMap = MapDatabase.GetMapByIndex(saveData.currentMapIndex);
@@ -271,33 +426,298 @@ public class GameFlowManager : MonoBehaviour {
EnsureOverlay();
ClearOverlay();
SetOverlayVisible(true);
SetDefaultOverlayBackground();
BuildResultScreen();
}
string title = lastResultCleared ? "맵 클리어" : "일정 실패";
string body = completedMap.displayName
+ "\n점수: " + lastResultScore
+ "\n획득 마일리지: " + lastResultMileage
+ "\n거리: " + Mathf.FloorToInt(lastResultDistance) + "m / " + Mathf.FloorToInt(completedMap.targetDistance) + "m"
+ "\n시간: " + lastResultTime.ToString("0.0") + "s";
private void BuildResultScreen() {
ApplyResultAirportBackground();
MapDefinition resultMap = completedMap != null ? completedMap : currentMap;
int rewardMileage = lastResultCleared && resultMap != null ? resultMap.clearMileageReward : 0;
int gainedMileage = Mathf.Max(0, lastResultMileage) + rewardMileage;
int targetDistance = resultMap != null ? Mathf.FloorToInt(resultMap.targetDistance) : 0;
int shownDistance = targetDistance > 0
? Mathf.Clamp(Mathf.FloorToInt(lastResultDistance), 0, targetDistance)
: Mathf.Max(0, Mathf.FloorToInt(lastResultDistance));
RectTransform passportRect = AddResultPassportPanel();
AddResultHeader(passportRect, resultMap);
AddResultStats(passportRect, gainedMileage, shownDistance, targetDistance);
AddResultTickets(passportRect, resultMap, rewardMileage);
if(lastResultCleared)
{
body += "\n클리어 보상: " + completedMap.clearMileageReward + " 마일리지";
}
AddTitle(title);
AddBody(body);
if(lastResultCleared)
{
AddButton("면세점으로", ShowDutyFreeShop);
AddResultActionButton("면세점으로", ShowDutyFreeShop, new Vector2(-166f, -282f), new Vector2(250f, 48f), true, LoadUiSprite("", DutyFreeIconAssetPath));
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(160f, -282f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
}
else
{
AddButton("다시 도전", RetryCurrentMap);
AddResultActionButton("다시 도전", RetryCurrentMap, new Vector2(-166f, -282f), new Vector2(250f, 48f), true, LoadUiSprite("", CheckpointIconAssetPath));
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(160f, -282f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
}
}
private void ApplyResultAirportBackground() {
if(overlayBackground != null)
{
overlayBackground.sprite = LoadUiSprite(ResultAirportBackgroundResourcePath, "Assets/Resources/Map_Tutorial_Incheon_Airport.png");
overlayBackground.preserveAspect = false;
overlayBackground.color = overlayBackground.sprite != null
? Color.white
: new Color(0.008f, 0.024f, 0.034f, 0.96f);
}
AddButton("인트로", ShowIntro);
if(flowCamera != null)
{
flowCamera.backgroundColor = new Color(0.006f, 0.022f, 0.035f, 1f);
}
GameObject scrimObject = CreateUiObject("Result Airport Background Scrim", overlayRoot, typeof(Image));
RectTransform scrimRect = scrimObject.GetComponent<RectTransform>();
scrimRect.anchorMin = Vector2.zero;
scrimRect.anchorMax = Vector2.one;
scrimRect.offsetMin = Vector2.zero;
scrimRect.offsetMax = Vector2.zero;
Image scrimImage = scrimObject.GetComponent<Image>();
scrimImage.color = new Color(0.005f, 0.015f, 0.020f, 0.30f);
scrimImage.raycastTarget = false;
}
private RectTransform AddResultPassportPanel() {
GameObject panelObject = CreateUiObject("Result Passport Page", overlayRoot, typeof(Image));
RectTransform panelRect = panelObject.GetComponent<RectTransform>();
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
panelRect.pivot = new Vector2(0.5f, 0.5f);
panelRect.anchoredPosition = new Vector2(0f, 38f);
panelRect.sizeDelta = new Vector2(1092f, 330f);
Image panelImage = panelObject.GetComponent<Image>();
panelImage.sprite = LoadUiSprite(ResultPassportPanelResourcePath, "Assets/Resources/UI_LeftHudPassportHorizontalGridPanel.png");
panelImage.color = panelImage.sprite != null
? Color.white
: new Color(0.95f, 0.91f, 0.80f, 0.98f);
panelImage.preserveAspect = true;
panelImage.raycastTarget = false;
return panelRect;
}
private void AddResultHeader(RectTransform parent, MapDefinition resultMap) {
Sprite rankSprite = LoadUiSprite("", ClearRankIconAssetPath);
Sprite stampSprite = LoadUiSprite(DepartureStampResourcePath, "Assets/Resources/UI_DepartureStamp_Red.png");
AddResultImage(parent, "Result Rank Badge", rankSprite, new Vector2(170f, -106f), new Vector2(50f, 50f), true, Color.white);
string title = lastResultCleared ? "출국 성공!" : "일정 실패";
Color titleColor = lastResultCleared
? new Color(0.05f, 0.17f, 0.27f, 1f)
: new Color(0.52f, 0.08f, 0.07f, 1f);
AddResultText(parent, "Result Title", title, new Vector2(194f, -88f), new Vector2(210f, 50f), 28f, FontStyles.Bold, titleColor, TextAlignmentOptions.MidlineLeft);
string destination = GetResultDestinationName(resultMap);
AddResultText(parent, "Result Route", destination, new Vector2(260f, -140f), new Vector2(210f, 22f), 13f, FontStyles.Bold, new Color(0.25f, 0.42f, 0.50f, 0.96f), TextAlignmentOptions.MidlineLeft);
if(lastResultCleared)
{
AddResultImage(parent, "Result Departure Stamp", stampSprite, new Vector2(250f, -232f), new Vector2(108f, 108f), true, new Color(1f, 1f, 1f, 0.92f));
TextMeshProUGUI stampText = AddResultText(parent, "Result Stamp Text", "출국\n성공", new Vector2(228f, -218f), new Vector2(92f, 48f), 21f, FontStyles.Bold, new Color(0.70f, 0.04f, 0.03f, 0.96f), TextAlignmentOptions.Center);
stampText.lineSpacing = -16f;
}
else
{
AddResultText(parent, "Result Retry Note", "다시 준비해서\n출국 게이트까지", new Vector2(178f, -216f), new Vector2(200f, 56f), 17f, FontStyles.Bold, new Color(0.32f, 0.46f, 0.54f, 0.96f), TextAlignmentOptions.Center);
}
}
private void AddResultStats(RectTransform parent, int gainedMileage, int shownDistance, int targetDistance) {
AddResultStat(parent, "Mileage", LoadUiSprite("", MileageIconAssetPath), "마일리지", "+" + gainedMileage, new Vector2(478f, -90f));
AddResultStat(parent, "Time", LoadUiSprite("", TimerIconAssetPath), "도착 시간", lastResultTime.ToString("0.0") + "s", new Vector2(626f, -90f));
string distance = targetDistance > 0
? shownDistance + "/" + targetDistance + "m"
: shownDistance + "m";
AddResultStat(parent, "Distance", LoadUiSprite("", CheckpointIconAssetPath), "이동 거리", distance, new Vector2(770f, -90f));
}
private void AddResultTickets(RectTransform parent, MapDefinition resultMap, int rewardMileage) {
AddResultTicket(
parent,
"Destination",
LoadUiSprite("", CheckpointIconAssetPath),
"목적지",
GetResultDestinationName(resultMap),
new Vector2(410f, -200f));
AddResultTicket(
parent,
"Souvenir",
lastResultCleared ? GetResultSouvenirSprite(resultMap) : LoadUiSprite("", SouvenirCollectionIconAssetPath),
"보상",
lastResultCleared ? GetResultSouvenirName(resultMap) : "다음 도전",
new Vector2(560f, -200f));
AddResultTicket(
parent,
"Total Mileage",
LoadUiSprite("", MileageIconAssetPath),
"보유",
saveData.totalMileage + " M",
new Vector2(710f, -200f));
}
private void AddResultStat(RectTransform parent, string objectName, Sprite iconSprite, string label, string value, Vector2 centerPosition) {
AddResultImage(parent, "Result " + objectName + " Icon", iconSprite, centerPosition + new Vector2(0f, -4f), new Vector2(42f, 42f), true, Color.white);
AddResultText(parent, "Result " + objectName + " Value", value, centerPosition + new Vector2(-70f, -25f), new Vector2(140f, 24f), 15f, FontStyles.Bold, new Color(0.05f, 0.17f, 0.25f, 1f), TextAlignmentOptions.Center);
AddResultText(parent, "Result " + objectName + " Label", label, centerPosition + new Vector2(-70f, -48f), new Vector2(140f, 18f), 8f, FontStyles.Bold, new Color(0.42f, 0.58f, 0.64f, 0.92f), TextAlignmentOptions.Center);
}
private void AddResultTicket(RectTransform parent, string objectName, Sprite iconSprite, string label, string value, Vector2 topLeftPosition) {
AddResultImage(parent, "Result " + objectName + " Icon", iconSprite, topLeftPosition + new Vector2(28f, -40f), new Vector2(40f, 40f), true, Color.white);
AddResultText(parent, "Result " + objectName + " Label", label, topLeftPosition + new Vector2(56f, -19f), new Vector2(94f, 18f), 13f, FontStyles.Bold, new Color(0.43f, 0.58f, 0.64f, 0.96f), TextAlignmentOptions.MidlineLeft);
AddResultText(parent, "Result " + objectName + " Value", value, topLeftPosition + new Vector2(56f, -42f), new Vector2(112f, 26f), 12f, FontStyles.Bold, new Color(0.05f, 0.18f, 0.27f, 1f), TextAlignmentOptions.MidlineLeft);
}
private void AddResultActionButton(string label, UnityEngine.Events.UnityAction action, Vector2 position, Vector2 size, bool primary, Sprite iconSprite) {
GameObject buttonObject = CreateUiObject("Result Button " + label, overlayRoot, typeof(Image), typeof(Button));
RectTransform buttonRect = buttonObject.GetComponent<RectTransform>();
buttonRect.anchorMin = new Vector2(0.5f, 0.5f);
buttonRect.anchorMax = new Vector2(0.5f, 0.5f);
buttonRect.pivot = new Vector2(0.5f, 0.5f);
buttonRect.anchoredPosition = position;
buttonRect.sizeDelta = size;
Image buttonImage = buttonObject.GetComponent<Image>();
buttonImage.sprite = LoadUiSprite(ResultButtonResourcePath, ResultButtonAssetPath);
buttonImage.color = buttonImage.sprite != null
? (primary ? Color.white : new Color(0.86f, 0.94f, 1f, 0.94f))
: primary
? new Color(1f, 0.82f, 0.28f, 0.98f)
: new Color(0.82f, 0.91f, 0.96f, 0.90f);
buttonImage.preserveAspect = false;
buttonImage.raycastTarget = true;
Button button = buttonObject.GetComponent<Button>();
button.onClick.AddListener(action);
if(iconSprite != null)
{
AddResultImage(buttonRect, "Result Button Icon", iconSprite, new Vector2(34f, -(size.y * 0.5f)), new Vector2(24f, 24f), true, Color.white);
}
TextMeshProUGUI buttonText = AddResultText(buttonRect, "Result Button Label", label, new Vector2(iconSprite != null ? 54f : 0f, -1f), new Vector2(iconSprite != null ? size.x - 70f : size.x, size.y), 16f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 1f), TextAlignmentOptions.Center);
RectTransform textRect = buttonText.rectTransform;
textRect.anchorMin = new Vector2(0f, 0.5f);
textRect.anchorMax = new Vector2(0f, 0.5f);
textRect.pivot = new Vector2(0f, 0.5f);
buttonText.outlineColor = new Color(0f, 0.04f, 0.07f, 0.98f);
buttonText.outlineWidth = 0.12f;
}
private Image AddResultImage(RectTransform parent, string objectName, Sprite sprite, Vector2 position, Vector2 size, bool preserveAspect, Color color) {
GameObject imageObject = CreateUiObject(objectName, parent, typeof(Image));
RectTransform imageRect = imageObject.GetComponent<RectTransform>();
imageRect.anchorMin = new Vector2(0f, 1f);
imageRect.anchorMax = new Vector2(0f, 1f);
imageRect.pivot = new Vector2(0.5f, 0.5f);
imageRect.anchoredPosition = position;
imageRect.sizeDelta = size;
Image image = imageObject.GetComponent<Image>();
image.sprite = sprite;
image.color = sprite != null ? color : new Color(0.30f, 0.46f, 0.54f, 0.28f);
image.preserveAspect = preserveAspect;
image.raycastTarget = false;
return image;
}
private TextMeshProUGUI AddResultText(RectTransform parent, string objectName, string text, Vector2 position, Vector2 size, float fontSize, FontStyles fontStyle, Color color, TextAlignmentOptions alignment) {
GameObject textObject = CreateUiObject(objectName, parent);
RectTransform textRect = textObject.GetComponent<RectTransform>();
textRect.anchorMin = new Vector2(0f, 1f);
textRect.anchorMax = new Vector2(0f, 1f);
textRect.pivot = new Vector2(0f, 1f);
textRect.anchoredPosition = position;
textRect.sizeDelta = size;
TextMeshProUGUI textComponent = textObject.AddComponent<TextMeshProUGUI>();
textComponent.text = text;
textComponent.font = runtimeFont != null ? runtimeFont : textComponent.font;
textComponent.fontSize = fontSize;
textComponent.fontStyle = fontStyle;
textComponent.alignment = alignment;
textComponent.color = color;
textComponent.raycastTarget = false;
textComponent.textWrappingMode = TextWrappingModes.Normal;
textComponent.overflowMode = TextOverflowModes.Ellipsis;
textComponent.enableAutoSizing = true;
textComponent.fontSizeMin = Mathf.Max(8f, fontSize * 0.66f);
textComponent.fontSizeMax = fontSize;
return textComponent;
}
private string GetResultDestinationName(MapDefinition resultMap) {
if(resultMap == null)
{
return "여행지";
}
switch(resultMap.mapId)
{
case MapId.TutorialIncheon:
return "인천공항";
case MapId.Japan:
return "일본";
case MapId.China:
return "중국";
case MapId.America:
return "미국";
default:
return resultMap.displayName;
}
}
private string GetResultSouvenirName(MapDefinition resultMap) {
if(resultMap == null)
{
return "여행 도장";
}
switch(resultMap.mapId)
{
case MapId.TutorialIncheon:
return "공항 마그넷";
case MapId.Japan:
return "일본 마그넷";
case MapId.China:
return "여행 부적";
case MapId.America:
return "스카이라인";
default:
return "여행 도장";
}
}
private Sprite GetResultSouvenirSprite(MapDefinition resultMap) {
if(resultMap == null)
{
return LoadUiSprite("", SouvenirCollectionIconAssetPath);
}
switch(resultMap.mapId)
{
case MapId.TutorialIncheon:
return LoadUiSprite("", AirportSouvenirAssetPath);
case MapId.Japan:
return LoadUiSprite("", JapanSouvenirAssetPath);
case MapId.China:
return LoadUiSprite("", ChinaSouvenirAssetPath);
case MapId.America:
return LoadUiSprite("", AmericaSouvenirAssetPath);
default:
return LoadUiSprite("", SouvenirCollectionIconAssetPath);
}
}
private void ShowDutyFreeShop() {
@@ -313,20 +733,166 @@ public class GameFlowManager : MonoBehaviour {
EnsureOverlay();
ClearOverlay();
SetOverlayVisible(true);
SetDefaultOverlayBackground();
AddTitle("면세점");
AddBody("보유 마일리지: " + saveData.totalMileage
+ "\n도시락: " + saveData.lunchboxStock
+ " / 방어막: " + saveData.shieldStock
+ "\n운동화: " + saveData.speedShoesStock
+ " / 마일리지 카드: " + saveData.mileageCardStock);
BuildDutyFreeShopScreen();
}
AddButton("도시락 구매 " + LunchboxCost, BuyLunchbox);
AddButton("방어막 구매 " + ShieldCost, BuyShield);
AddButton("운동화 구매 " + SpeedShoesCost, BuySpeedShoes);
AddButton("마일리지 카드 구매 " + MileageCardCost, BuyMileageCard);
AddButton("다음 맵 보기", ShowMapPreview);
private void BuildDutyFreeShopScreen() {
ApplyDutyFreeShopBackground();
RectTransform boardRect = AddDutyFreeBoard();
AddDutyFreeHeader(boardRect);
AddDutyFreeItemCard(
boardRect,
"도시락",
"HP 회복",
LunchboxCost,
saveData.lunchboxStock,
LoadUiSprite("", DutyFreeLunchboxAssetPath),
BuyLunchbox,
new Vector2(58f, -130f));
AddDutyFreeItemCard(
boardRect,
"방어막",
"1회 방어",
ShieldCost,
saveData.shieldStock,
LoadUiSprite("", DutyFreeShieldAssetPath),
BuyShield,
new Vector2(296f, -130f));
AddDutyFreeItemCard(
boardRect,
"운동화",
"속도 회복",
SpeedShoesCost,
saveData.speedShoesStock,
LoadUiSprite("", DutyFreeShoesAssetPath),
BuySpeedShoes,
new Vector2(534f, -130f));
AddDutyFreeItemCard(
boardRect,
"마일리지 카드",
"획득량 증가",
MileageCardCost,
saveData.mileageCardStock,
LoadUiSprite("", DutyFreeMileageCardAssetPath),
BuyMileageCard,
new Vector2(772f, -130f));
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(0f, -292f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
}
private void ApplyDutyFreeShopBackground() {
if(overlayBackground != null)
{
overlayBackground.sprite = LoadUiSprite("", DutyFreeBackgroundAssetPath);
overlayBackground.preserveAspect = false;
overlayBackground.color = overlayBackground.sprite != null
? Color.white
: new Color(0.03f, 0.035f, 0.038f, 0.96f);
}
if(flowCamera != null)
{
flowCamera.backgroundColor = new Color(0.03f, 0.035f, 0.038f, 1f);
}
GameObject scrimObject = CreateUiObject("Duty Free Background Scrim", overlayRoot, typeof(Image));
RectTransform scrimRect = scrimObject.GetComponent<RectTransform>();
scrimRect.anchorMin = Vector2.zero;
scrimRect.anchorMax = Vector2.one;
scrimRect.offsetMin = Vector2.zero;
scrimRect.offsetMax = Vector2.zero;
Image scrimImage = scrimObject.GetComponent<Image>();
scrimImage.color = new Color(0.01f, 0.012f, 0.014f, 0.34f);
scrimImage.raycastTarget = false;
}
private RectTransform AddDutyFreeBoard() {
GameObject boardObject = CreateUiObject("Duty Free Product Board", overlayRoot, typeof(Image));
RectTransform boardRect = boardObject.GetComponent<RectTransform>();
boardRect.anchorMin = new Vector2(0.5f, 0.5f);
boardRect.anchorMax = new Vector2(0.5f, 0.5f);
boardRect.pivot = new Vector2(0.5f, 0.5f);
boardRect.anchoredPosition = new Vector2(0f, 28f);
boardRect.sizeDelta = new Vector2(1038f, 462f);
Image boardImage = boardObject.GetComponent<Image>();
boardImage.color = new Color(0.015f, 0.055f, 0.085f, 0.84f);
boardImage.raycastTarget = false;
return boardRect;
}
private void AddDutyFreeHeader(RectTransform parent) {
AddResultImage(parent, "Duty Free Header Icon", LoadUiSprite("", DutyFreeIconAssetPath), new Vector2(84f, -60f), new Vector2(48f, 48f), true, Color.white);
AddResultText(parent, "Duty Free Title", "면세점", new Vector2(120f, -38f), new Vector2(180f, 46f), 32f, FontStyles.Bold, new Color(0.96f, 0.99f, 1f, 1f), TextAlignmentOptions.MidlineLeft);
AddResultText(parent, "Duty Free Subtitle", "다음 여행 준비", new Vector2(122f, -82f), new Vector2(220f, 24f), 15f, FontStyles.Bold, new Color(0.64f, 0.82f, 0.92f, 0.96f), TextAlignmentOptions.MidlineLeft);
AddResultImage(parent, "Duty Free Mileage Icon", LoadUiSprite("", MileageIconAssetPath), new Vector2(760f, -61f), new Vector2(42f, 42f), true, Color.white);
AddResultText(parent, "Duty Free Mileage Label", "보유 마일리지", new Vector2(794f, -43f), new Vector2(140f, 22f), 13f, FontStyles.Bold, new Color(0.64f, 0.82f, 0.92f, 0.96f), TextAlignmentOptions.MidlineLeft);
AddResultText(parent, "Duty Free Mileage Value", saveData.totalMileage + " M", new Vector2(794f, -66f), new Vector2(160f, 32f), 24f, FontStyles.Bold, new Color(1f, 0.90f, 0.38f, 1f), TextAlignmentOptions.MidlineLeft);
}
private void AddDutyFreeItemCard(RectTransform parent, string itemName, string effectText, int cost, int stock, Sprite itemSprite, UnityEngine.Events.UnityAction buyAction, Vector2 topLeftPosition) {
bool canBuy = saveData.totalMileage >= cost;
GameObject cardObject = CreateUiObject("Duty Free Item " + itemName, parent, typeof(Image), typeof(Button));
RectTransform cardRect = cardObject.GetComponent<RectTransform>();
cardRect.anchorMin = new Vector2(0f, 1f);
cardRect.anchorMax = new Vector2(0f, 1f);
cardRect.pivot = new Vector2(0f, 1f);
cardRect.anchoredPosition = topLeftPosition;
cardRect.sizeDelta = new Vector2(206f, 242f);
Image cardImage = cardObject.GetComponent<Image>();
cardImage.color = canBuy
? new Color(0.97f, 0.91f, 0.78f, 0.96f)
: new Color(0.40f, 0.45f, 0.48f, 0.88f);
cardImage.raycastTarget = true;
Button button = cardObject.GetComponent<Button>();
button.targetGraphic = cardImage;
button.interactable = canBuy;
button.onClick.AddListener(buyAction);
button.navigation = new Navigation {
mode = Navigation.Mode.None
};
Color textColor = canBuy
? new Color(0.06f, 0.16f, 0.22f, 1f)
: new Color(0.78f, 0.84f, 0.86f, 1f);
Color subColor = canBuy
? new Color(0.34f, 0.49f, 0.54f, 0.98f)
: new Color(0.66f, 0.72f, 0.74f, 0.98f);
AddResultImage(cardRect, "Duty Free " + itemName + " Image", itemSprite, new Vector2(103f, -70f), new Vector2(100f, 88f), true, canBuy ? Color.white : new Color(1f, 1f, 1f, 0.54f));
AddResultText(cardRect, "Duty Free " + itemName + " Name", itemName, new Vector2(16f, -116f), new Vector2(174f, 28f), 20f, FontStyles.Bold, textColor, TextAlignmentOptions.Center);
AddResultText(cardRect, "Duty Free " + itemName + " Effect", effectText, new Vector2(16f, -146f), new Vector2(174f, 22f), 13f, FontStyles.Bold, subColor, TextAlignmentOptions.Center);
AddResultText(cardRect, "Duty Free " + itemName + " Stock", "보유 " + stock, new Vector2(20f, -174f), new Vector2(72f, 22f), 13f, FontStyles.Bold, subColor, TextAlignmentOptions.MidlineLeft);
AddResultText(cardRect, "Duty Free " + itemName + " Cost", cost + " M", new Vector2(102f, -174f), new Vector2(84f, 22f), 15f, FontStyles.Bold, new Color(0.87f, 0.55f, 0.08f, 1f), TextAlignmentOptions.MidlineRight);
GameObject stripObject = CreateUiObject("Duty Free " + itemName + " Buy Strip", cardRect, typeof(Image));
RectTransform stripRect = stripObject.GetComponent<RectTransform>();
stripRect.anchorMin = new Vector2(0.5f, 0f);
stripRect.anchorMax = new Vector2(0.5f, 0f);
stripRect.pivot = new Vector2(0.5f, 0f);
stripRect.anchoredPosition = new Vector2(0f, 14f);
stripRect.sizeDelta = new Vector2(160f, 34f);
Image stripImage = stripObject.GetComponent<Image>();
stripImage.sprite = LoadUiSprite(ResultButtonResourcePath, ResultButtonAssetPath);
stripImage.color = canBuy ? Color.white : new Color(0.60f, 0.65f, 0.68f, 0.88f);
stripImage.preserveAspect = false;
stripImage.raycastTarget = false;
TextMeshProUGUI stripText = AddResultText(cardRect, "Duty Free " + itemName + " Buy Label", canBuy ? "구매" : "마일리지 부족", new Vector2(23f, -200f), new Vector2(160f, 32f), 14f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 1f), TextAlignmentOptions.Center);
stripText.outlineColor = new Color(0f, 0.04f, 0.07f, 0.98f);
stripText.outlineWidth = 0.12f;
}
private void ShowMapPreview() {
@@ -384,6 +950,11 @@ public class GameFlowManager : MonoBehaviour {
case FlowScreen.OpeningStory:
ShowOpeningStory();
break;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
case FlowScreen.DebugMenu:
ShowDebugMenu();
break;
#endif
case FlowScreen.Intro:
default:
ShowIntro();
@@ -474,6 +1045,7 @@ public class GameFlowManager : MonoBehaviour {
private void EnsureOverlay() {
EnsureEventSystem();
EnsureFlowCamera();
if(overlayCanvas != null)
{
@@ -487,6 +1059,7 @@ public class GameFlowManager : MonoBehaviour {
overlayCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
overlayCanvas.worldCamera = null;
overlayCanvas.sortingOrder = 100;
overlayCanvas.targetDisplay = 0;
SetUiLayer(canvasObject);
CanvasScaler scaler = canvasObject.GetComponent<CanvasScaler>();
@@ -515,6 +1088,8 @@ public class GameFlowManager : MonoBehaviour {
private void SetDefaultOverlayBackground() {
if(overlayBackground != null)
{
overlayBackground.sprite = null;
overlayBackground.preserveAspect = false;
overlayBackground.color = new Color(0.02f, 0.035f, 0.045f, 0.92f);
}
@@ -527,6 +1102,8 @@ public class GameFlowManager : MonoBehaviour {
private void BuildIntroScreen() {
if(overlayBackground != null)
{
overlayBackground.sprite = null;
overlayBackground.preserveAspect = false;
overlayBackground.color = Color.clear;
}
@@ -547,6 +1124,8 @@ public class GameFlowManager : MonoBehaviour {
private void BuildOpeningStoryScreen() {
if(overlayBackground != null)
{
overlayBackground.sprite = null;
overlayBackground.preserveAspect = false;
overlayBackground.color = new Color(0.012f, 0.018f, 0.024f, 0.96f);
}
@@ -976,9 +1555,9 @@ public class GameFlowManager : MonoBehaviour {
stampRect.localRotation = Quaternion.Euler(0f, 0f, -12f);
Image stampImage = stampObject.GetComponent<Image>();
stampImage.sprite = LoadUiSpriteRegion("Assets/ConceptArt/Intro/intro_stamps_01.png", new Rect(54f, 48f, 790f, 790f));
stampImage.sprite = LoadUiSprite(DepartureStampResourcePath, "Assets/Resources/UI_DepartureStamp_Red.png");
stampImage.color = stampImage.sprite != null
? new Color(1f, 1f, 1f, 0.94f)
? new Color(1f, 1f, 1f, 0.96f)
: new Color(0.76f, 0.08f, 0.06f, 0.96f);
stampImage.preserveAspect = true;
stampImage.raycastTarget = false;
@@ -1274,6 +1853,8 @@ public class GameFlowManager : MonoBehaviour {
private void EnsureFlowCamera() {
if(flowCamera != null)
{
flowCamera.enabled = true;
flowCamera.targetDisplay = 0;
return;
}
@@ -1288,6 +1869,7 @@ public class GameFlowManager : MonoBehaviour {
flowCamera.depth = 100f;
flowCamera.orthographic = true;
flowCamera.orthographicSize = 360f;
flowCamera.targetDisplay = 0;
}
private void ClearOverlay() {
@@ -1303,6 +1885,11 @@ public class GameFlowManager : MonoBehaviour {
}
private void SetOverlayVisible(bool visible) {
if(visible)
{
EnsureFlowCamera();
}
if(overlayCanvas != null)
{
overlayCanvas.gameObject.SetActive(visible);