Files
blockout/BlockOutReplica/Assets/Scripts/Presentation/BlockOutHudInteractions.cs
T
2026-10-20 13:21:04 +08:00

287 lines
19 KiB
C#

using System;
using BlockOutReplica.Gameplay;
using UnityEngine;
namespace BlockOutReplica.Presentation
{
public sealed partial class BlockOutHud
{
private void DrawInteractionOverlay(float left, float scale)
{
if (game.Modal == GameModal.None) return;
Color previous = GUI.color;
GUI.color = new Color(0,0,0,game.Modal==GameModal.Quit?.89411765f:.72f);
GUI.DrawTexture(new Rect(0,0,Screen.width,Screen.height), Texture2D.whiteTexture);
GUI.color = previous;
// These layouts come from the original popup prefabs. Do not
// force them into the generic purchase/confirmation dialog layout.
if (game.Modal == GameModal.PreLevel) { DrawEntrancePopup(left, scale); return; }
if (game.Modal == GameModal.Pause) { DrawPausePopup(left, scale); return; }
if (game.Modal == GameModal.Quit) { DrawQuitPopup(left, scale); return; }
if (game.Modal == GameModal.Failed) { DrawFailedPopup(left, scale); return; }
if (game.Modal == GameModal.OutOfTime) { DrawOutOfTimePopup(left, scale); return; }
DrawNineSlice(Load("Popup_BG_Default_0"), D(left, scale, 55.3f, 181.3f, 429.3f, 557.2f), scale * .46875f);
GUIStyle text = CreateTextStyle(Mathf.RoundToInt(20 * scale));
GUIStyle small = CreateTextStyle(Mathf.RoundToInt(15 * scale));
string heading = game.Modal == GameModal.Pause ? "Pause" : game.Modal == GameModal.Restart ? "Restart?" :
game.Modal == GameModal.Quit ? "Quit level?" : game.Modal == GameModal.OutOfTime ? "Out of Time" :
game.Modal == GameModal.GiveUp ? "Give up?" : game.Modal == GameModal.Failed ? "Failed" :
game.Modal == GameModal.PreLevel ? "Level " + game.LevelNumber : game.Modal == GameModal.Settings ? "Settings" :
game.Modal == GameModal.Lives ? "Lives" : game.Modal == GameModal.BoosterPurchase ? "Get " + game.PurchaseBooster : "Information";
DrawOriginalPopupTitle(D(left, scale, 85, 160, 370, 73), heading, scale);
DrawPopupClose(left, scale, false);
Rect primary = D(left, scale, 135, 551, 270, 68);
Rect secondary = D(left, scale, 135, 637, 270, 58);
switch (game.Modal)
{
case GameModal.Settings:
DrawSettingRow(left, scale, 320, "Sounds", game.Player.Sounds, game.ToggleSounds);
DrawSettingRow(left, scale, 384, "Music", game.Player.Music, game.ToggleMusic);
DrawSettingRow(left, scale, 448, "Haptics", game.Player.Haptics, game.ToggleHaptics);
if (ActionButton(primary, "Done", scale)) game.CloseModal();
break;
case GameModal.Restart:
case GameModal.Quit:
DrawText(game.HasStarted ? "You will lose 1 life!" : "This level has not started.", D(left, scale, 80, 350, 380, 65), text);
DrawSprite(Load("MoreLives_Icon_Heart"), D(left, scale, 225, 414, 90, 80));
if (ActionButton(primary, game.Modal == GameModal.Restart ? "Restart" : "Quit", scale, true))
{ if (game.Modal == GameModal.Restart) game.ConfirmRestart(); else game.ConfirmQuit(); }
if (ActionButton(secondary, "Cancel", scale)) game.CloseModal();
break;
case GameModal.GiveUp:
DrawSprite(clockIcon, D(left, scale, 220, 318, 100, 100));
DrawText("You will lose 1 life!", D(left, scale, 75, 433, 390, 60), text);
if (ActionButton(primary, "Play On " + game.ContinueCost, scale)) game.ContinueWithCoins();
if (ActionButton(secondary, "Give up", scale, true)) game.GiveUpRun();
break;
case GameModal.BoosterPurchase:
int index = (int)game.PurchaseBooster - 1;
DrawSprite(boosterIcons[Mathf.Clamp(index,0,2)], D(left, scale, 220, 326, 100, 100));
DrawText("Buy 1 booster with coins", D(left, scale, 90, 447, 360, 55), text);
if (ActionButton(primary, "Buy 1 " + game.BoosterCost, scale)) game.BuySelectedBooster();
if (ActionButton(secondary, "Cancel", scale, true)) game.CloseModal();
break;
case GameModal.Lives:
DrawSprite(Load("MoreLives_Icon_Heart"), D(left, scale, 215, 315, 110, 105));
DrawText($"{game.Player.Lives} / {game.Player.MaxLives}", D(left, scale, 170, 422, 200, 50), text);
double seconds = game.Player.SecondsUntilLife;
DrawText(game.Player.Lives >= game.Player.MaxLives ? "Lives are full" : $"Next life {(int)seconds / 60:00}:{(int)seconds % 60:00}",
D(left, scale, 90, 482, 360, 42), small);
bool enabled = GUI.enabled; GUI.enabled = game.Player.Lives < game.Player.MaxLives;
if (ActionButton(primary, "Refill " + game.LifeRefillCost, scale)) game.BuyLifeRefill();
GUI.enabled = enabled;
if (ActionButton(secondary, "Back", scale)) game.CloseModal();
break;
case GameModal.Info:
DrawText(game.Notice, D(left, scale, 90, 340, 360, 170), text);
if (ActionButton(primary, "OK", scale)) game.CloseModal();
break;
}
if (!string.IsNullOrEmpty(game.Notice) && game.Modal != GameModal.Info)
DrawText(game.Notice, D(left, scale, 90, 710, 360, 42), small);
}
private void DrawEntrancePopup(float left, float scale)
{
// Original L10 runtime collapses the unused supply-box section.
// These dimensions come from the observed 882x1644 screen (game
// viewport y=76..1644), not the prefab's expanded initial size.
DrawNineSlice(Load("Popup_BG_Default_0"), D(left, scale, 65, 296, 413, 418), scale * .46875f);
DrawOriginalPopupTitle(D(left, scale, 70, 239, 400, 106), "Level " + game.LevelNumber, scale, 60);
DrawPopupClose(left, scale, true);
DrawMirroredSlicedPanel(Load("Prelevel_Booster_Area_Default"),
D(left, scale, 107, 376, 326, 145), scale * .46875f);
DrawText("Select Boosters:", D(left, scale, 170, 389, 200, 28), PopupTextStyle(21, scale, false));
DrawLockedPreBooster(left, scale, 214.12f, 462.8f);
DrawLockedPreBooster(left, scale, 327.75f, 463.64f);
if (PopupActionButton(D(left, scale, 152.35f, 566.06f, 235.3f, 97.97f), "Play", scale,
"Prelevel_Btn_Play", "Prelevel_Btn_Play_Bg_Default", 52)) game.EnterPreparedLevel();
DrawPopupNotice(left, scale);
}
private void DrawLockedPreBooster(float left, float scale, float cx, float cy)
{
DrawMirroredButton(Load("Prelevel_Booster_Btn_Bg_Default"), D(left, scale, cx - 45.47f, cy - 45.7f, 90.94f, 91.4f));
DrawMirroredButton(Load("Prelevel_Booster_Btn_Blue"), D(left, scale, cx - 39.375f, cy - 39.375f, 78.75f, 78.75f));
DrawSprite(Load("PrelevelPopup_Lock_Icon"), D(left, scale, cx - 22.27f, cy - 27.19f, 44.53f, 54.38f));
// There is no verified unlock threshold or purchase action for these
// pre-boosters. Never route them to the in-level booster inventory.
}
private void DrawPausePopup(float left, float scale)
{
DrawNineSlice(Load("Popup_BG_Default_0"), D(left, scale, 55.3f, 181.3f, 429.3f, 557.2f), scale * .46875f);
DrawOriginalPopupTitle(D(left, scale, 85, 160.05f, 370, 73), "Pause", scale);
DrawPopupClose(left, scale, false);
DrawOriginalSettingRow(left, scale, 297.06f, "Sounds:", "Pause_Icon_Sounds", game.Player.Sounds, game.ToggleSounds);
DrawOriginalSettingRow(left, scale, 385.05f, "Musics:", "Pause_Icon_Musics", game.Player.Music, game.ToggleMusic);
DrawSprite(Load("Pause_Horizontal_Seperator"),D(left,scale,119,437,302,9));
if (PopupActionButton(D(left, scale, 163.83f, 478.13f, 212.34f, 88.13f), "Resume", scale,
"Pause_Btn_Resume", "Pause_Btn_BG", 35)) game.CloseModal();
if (PopupActionButton(D(left, scale, 163.83f, 586.88f, 212.34f, 88.13f), "Quit", scale,
"Pause_Btn_Quit", "Pause_Btn_BG", 35)) game.RequestQuit();
DrawPopupNotice(left, scale);
}
private void DrawOriginalSettingRow(float left, float scale, float cy, string label, string icon, bool value, Action toggle)
{
DrawSprite(Load(icon), D(left, scale, 129.8f, cy - 18, 33, 36));
DrawOriginalText(label,D(left,scale,175.4f,cy-26,110,52),Mathf.RoundToInt(26*scale),OriginalTextRole.PopupLabel);
Rect track = D(left, scale, 287.56f, cy - 26.74f, 134.79f, 53.47f);
DrawNineSlice(Load("Pause_Toggle_BG"), track, scale * .46875f);
Rect off = D(left, scale, 291.71f, cy - 24.14f, 64.22f, 48.28f);
Rect on = D(left, scale, 354.05f, cy - 24.14f, 64.22f, 48.28f);
DrawSprite(Load(value ? "Pause_Toggle_Btn_On" : "Pause_Toggle_Btn_Off"), value ? on : off);
var offStyle=PopupTextStyle(23,scale,false);offStyle.normal.textColor=new Color(.22f,.16f,.62f);
var onStyle=PopupTextStyle(23,scale,false);onStyle.normal.textColor=value?new Color(.16f,.29f,.08f):new Color(.22f,.16f,.62f);
DrawText("Off", off, offStyle);DrawText("On", on, onStyle);
if (GUI.Button(track, GUIContent.none, GUIStyle.none)) { game.PlayUiSound(); toggle(); }
}
private void DrawFailedPopup(float left, float scale)
{
DrawNineSlice(Load("Popup_BG_Default_0"), D(left, scale, 54.94f, 182.30f, 430.13f, 556.21f), scale * .46875f);
Sprite titleArt = Load("Fail_Popup_Title_Default");
if (titleArt != null) DrawSprite(titleArt, D(left, scale, 151.17f, 151.05f, 237.66f, 139.22f));
else DrawOriginalPopupTitle(D(left, scale, 85, 160, 370, 73), "Failed", scale);
DrawPopupClose(left, scale, false);
DrawOriginalTitle("Level "+game.LevelNumber,D(left,scale,100,306,340,66),Mathf.RoundToInt(40*scale));
DrawSprite(Load("Fail_Popup_Coins"),D(left,scale,162,395,207,115));
DrawSlicedSprite(Load("Fail_Popup_Coins_Counter_Default"),D(left,scale,211,488,122,53));
DrawOriginalText(game.LevelRewardPreview.ToString(),D(left,scale,230,489,64,48),Mathf.RoundToInt(41*scale),
OriginalTextRole.Counter,TextAnchor.MiddleCenter,new Color(1,.76f,.25f));
DrawSlicedSprite(Load("Fail_Popup_X_Icon"),D(left,scale,303,476,65,65),OriginalSpriteLayout.MirroredHalf);
if (PopupActionButton(D(left, scale, 152.35f, 588.5f, 235.3f, 98), "Try Again", scale,
"Fail_Popup_Btn", "Fail_Popup_Btn_Stroke", 39)) game.RetryFailedLevel();
// Preserve the replica's Failed CloseModal -> GoHome callback; no
// extra red Home button is added below the native Try Again button.
DrawPopupNotice(left, scale);
}
private void DrawQuitPopup(float left,float scale)
{
// Observed L10 runtime: this banner extends to both viewport edges.
// Its narrow left strip provides the native horizontal rails; the
// rounded right cap from the prefab is outside the visible viewport.
DrawSprite(Load("Endgame_Popup_Left_Default"),new Rect(0,229*scale,Screen.width,471.4f*scale));
DrawSprite(Load("Endgame_Popup_Inside_Default"),new Rect(0,302*scale,Screen.width,203*scale));
DrawOriginalText("Are You Sure?",D(left,scale,90,252,360,51),Mathf.RoundToInt(39*scale),OriginalTextRole.PopupLabel);
DrawSprite(Load("broken_heart"),D(left,scale,189.8f,343.5f,159.2f,120.6f));
DrawOriginalText("You will lose 1 life!",D(left,scale,105,516,330,35),Mathf.RoundToInt(26*scale),OriginalTextRole.PopupLabel);
Rect coins=D(left,scale,71.6f,175.1f,115.1f,40.4f);
DrawSlicedSprite(Load("Endgame_Coin_BG_Defaut"),D(left,scale,91.6f,175.1f,95.1f,40.4f));
DrawSprite(Load("Endgame_Coin_Icon"),D(left,scale,71.6f,175.1f,40,40));
DrawOriginalText(game.Player.Coins.ToString(),D(left,scale,116,177,64,35),Mathf.RoundToInt(28*scale),OriginalTextRole.Counter);
Rect close=D(left,scale,421.8f,218,52,50.8f);
DrawSprite(Load("Settings_Exit_Btn_0"),close);
DrawSprite(Load("Settings_Exit_Icon_1"),D(left,scale,434.5f,229.5f,26.3f,27.2f));
if(GUI.Button(close,GUIContent.none,GUIStyle.none)){game.PlayUiSound();game.CloseModal();}
if(PopupActionButton(D(left,scale,150.6f,562,240.6f,100.4f),"Quit",scale,
"Endgame_Btn_Quit","Endgame_Btn_Stroke_Quit",43))game.ConfirmQuit();
}
private void DrawOutOfTimePopup(float left, float scale)
{
// StuckPopup has a full-screen dark backdrop, not Popup_BG_Default.
// Only its coin continuation branch is currently implemented. The
// prefab also contains free/ad variants; their availability is not
// established, so no non-functional ad button is drawn here.
DrawOriginalPopupTitle(D(left, scale, 85, 180.8f, 370, 73), "Out of Time", scale);
DrawSprite(Load("icon_notime") ?? clockIcon, D(left, scale, 132.98f, 297.11f, 272.34f, 281.72f));
DrawText("+30 seconds", D(left, scale, 110, 582.05f, 320, 72), PopupTextStyle(32, scale, true));
Rect button = D(left, scale, 115, 713.6f, 310, 98);
Rect frame = new Rect(button.x - 7 * scale, button.y - 7 * scale, button.width + 14 * scale, button.height + 14 * scale);
DrawMirroredButton(Load("Home_Button_Bg"), frame);
DrawMirroredButton(Load("Home_Button_Green_0") ?? Load("Home_Button_Green"), button);
DrawText("Play On", D(left, scale, 135, 723.3f, 166, 68), PopupTextStyle(29, scale, true));
DrawSprite(Load("icon_coin_bttn"), D(left, scale, 291, 735.6f, 35, 36));
DrawText(game.ContinueCost.ToString(), D(left, scale, 330, 723.3f, 80, 68), PopupTextStyle(27, scale, true));
if (GUI.Button(frame, GUIContent.none, GUIStyle.none)) { game.PlayUiSound(); game.ContinueWithCoins(); }
Rect close = D(left, scale, 423.55f, 57.13f, 48.75f, 48.75f);
DrawSprite(Load("Settings_Exit_Btn"), close);
DrawSprite(Load("Settings_Exit_Icon_1") ?? Load("Settings_Exit_Icon"), D(left, scale, 434.8f, 67.9f, 26.25f, 27.19f));
if (GUI.Button(close, GUIContent.none, GUIStyle.none)) { game.PlayUiSound(); game.CloseModal(); }
if (!string.IsNullOrEmpty(game.Notice))
DrawText(game.Notice, D(left, scale, 90, 839, 360, 48), PopupTextStyle(17, scale, true));
}
private void DrawPopupClose(float left, float scale, bool entrance)
{
float cy = entrance ? 341f : 236.11f;
Rect body = D(left, scale, 425.2f, cy - 26.72f, 53.44f, 53.44f);
DrawSprite(Load(entrance ? "Settings_Exit_Btn_1" : "Settings_Exit_Btn_0") ?? Load("Settings_Exit_Btn"), body);
DrawSprite(Load("Settings_Exit_Icon_1") ?? Load("Settings_Exit_Icon"), D(left, scale, 438.79f, cy - 13.59f, 26.25f, 27.19f));
if (GUI.Button(body, GUIContent.none, GUIStyle.none)) { game.PlayUiSound(); game.CloseModal(); }
}
private void DrawOriginalPopupTitle(Rect rect, string label, float scale, int fontSize = 60)
{
DrawOriginalTitle(label,rect,Mathf.RoundToInt(fontSize*scale));
}
private GUIStyle PopupTextStyle(int size, float scale, bool white)
{
GUIStyle style = CreateOriginalTextStyle(Mathf.RoundToInt(size*scale),white?OriginalTextRole.ButtonGreen:OriginalTextRole.Body);
style.normal.textColor = white ? Color.white : new Color(.22745f, .16863f, .63137f, 1);
return style;
}
private bool PopupActionButton(Rect rect, string label, float scale, string face, string frame, int fontSize)
{
Rect background = new Rect(rect.x - 6 * scale, rect.y - 5 * scale, rect.width + 12 * scale, rect.height + 12 * scale);
DrawMirroredButton(Load(frame), background);
DrawMirroredButton(Load(face), rect);
Rect textRect = rect; textRect.y -= 2 * scale;
DrawOriginalText(label,textRect,Mathf.RoundToInt(fontSize*scale),
face.IndexOf("Quit",StringComparison.Ordinal)>=0?OriginalTextRole.ButtonRed:OriginalTextRole.ButtonGreen);
if (!GUI.Button(background, GUIContent.none, GUIStyle.none)) return false;
game.PlayUiSound(); return true;
}
private void DrawPopupNotice(float left, float scale)
{
if (!string.IsNullOrEmpty(game.Notice))
DrawText(game.Notice, D(left, scale, 90, 699, 360, 42), PopupTextStyle(15, scale, false));
}
private static void DrawMirroredSlicedPanel(Sprite sprite, Rect target, float scale)
{
DrawSlicedSprite(sprite, target, OriginalSpriteLayout.MirroredHalf, scale);
}
private void DrawSettingRow(float left, float scale, float y, string label, bool value, Action toggle)
{
var style = CreateTextStyle(Mathf.RoundToInt(21 * scale));
DrawText(label, D(left, scale, 98, y, 180, 45), style);
Rect button = D(left, scale, 303, y, 113, 45);
DrawSprite(Load("Pause_Toggle_BG"), button);
DrawSprite(Load(value ? "Pause_Toggle_Btn_On" : "Pause_Toggle_Btn_Off"), button);
DrawText(value ? "On" : "Off", button, style);
if (GUI.Button(button, GUIContent.none, GUIStyle.none)) toggle();
}
private bool ActionButton(Rect rect, string label, float scale, bool red = false)
{
DrawMirroredButton(Load(red ? "Home_Button_Red" : "Home_Button_Green"), rect);
DrawText(label, rect, CreateTextStyle(Mathf.RoundToInt(23 * scale)));
if (!GUI.Button(rect, GUIContent.none, GUIStyle.none)) return false;
game.PlayUiSound(); return true;
}
private static void DrawNineSlice(Sprite sprite, Rect target, float scale)
{
if (sprite == null || sprite.texture == null)
{
Color previous = GUI.color; GUI.color = new Color(.23f,.15f,.58f,.98f);
GUI.DrawTexture(target, Texture2D.whiteTexture); GUI.color = previous; return;
}
DrawSlicedSprite(sprite, target, OriginalSpriteLayout.NineSlice, scale);
}
private static void DrawMirroredButton(Sprite sprite, Rect target)
{
DrawSlicedSprite(sprite, target, OriginalSpriteLayout.MirroredHalf);
}
}
}