CHAPTER 01Start here: Bubble Spire
Bubble Spire is a complete editable Unity game template. Drag horizontally to rotate the tower, then tap a tower position to shoot. Connected groups of three or more same-colour marbles pop; marbles detached from the top fall. Complete colour quotas before the marble budget runs out.
Included: 25 curated levels, seeded continuation, visual level editor and validation, four guided helpers and portrait/landscape scenes. Saving is local. No multiplayer server, purchases, HLK, cloud accounts or remote LiveOps backend is included. Keep the required private game Kit. Szekipapa77 Ads is publisher-owned integration code; provider SDK binaries are excluded. Import only one game Kit per project because they share class names.
CHAPTER 02Installation and first play
Use Unity 6000.0.62f1, URP 17.0.4 and Unity UI 2.0.0. Retain the supplied official manifest and ProjectSettings. Install Android SDK/NDK/OpenJDK and WebGL support through matching Unity Hub modules.
- Extract the standalone project to a short writable path and open it with the matching editor. Let package import finish.
- For native package import, create a matching URP project, choose Assets > Import Package > Custom Package and import every game, Kit, Ads and WebGL template entry.
- Run Szekipapa77 > Bubble Spire > Setup. This configures rendering and player settings; inspect changes when combining with an existing project.
- Set Active Input Handling to Input Manager (Old), or Both if your project also has the Input System. New Input System only is insufficient.
- Open
Assets/Games/BubbleTower/Scenes/BubbleTower_Mobile.unity, press Play, dismiss the level card and follow the first-level guidance. - Stop Play mode before data editing. Retain .meta files/GUIDs. No separate TextMesh Pro import is needed by the classic Unity UI Text path.
CHAPTER 03Project map and actual scripts
All paths here are under Assets/Games/BubbleTower. Scripts owns gameplay; Editor owns visual tools; Kit supplies UI, audio, saving and platform code; Resources contains data/art/audio; Settings contains URP; Scenes contains mobile and WebGL entry points. The UI is created in code at runtime, not authored as a persistent Canvas prefab.
Scripts/BtData.csScripts/BtGen.csScripts/BtLevels.csScripts/BtLogic.csScripts/BtMeshes.csScripts/BtSfx.csScripts/BubbleTowerGame.csScripts/BubbleTowerGame.Hud.csScripts/BubbleTowerGame.View.cs
Read BubbleTowerGame.cs for input/flow, BubbleTowerGame.Hud.cs for menus/popups, BubbleTowerGame.View.cs for the board/view, BtData.cs for the model and BtLevels.cs for level loading. Advertising settings are outside the game at Assets/Szekipapa77Ads.
CHAPTER 04Gameplay, input and progression
Drag horizontally to rotate the tower, then tap a tower position to shoot. Connected groups of three or more same-colour marbles pop; marbles detached from the top fall. Complete colour quotas before the marble budget runs out.
The curated bank contains 25 authored levels followed by generated continuation. The mobile scene requests portrait, while the WebGL-named scene requests landscape. Supplied build menu targets currently choose the portrait scene for both platforms; choose the landscape scene explicitly in a Build Profile if desired. Test layouts and controls after a near-startup scene replacement as well as after a finished round.
CHAPTER 05Tutorials and all four helpers
The first level includes guided controls; helper unlocks introduce each helper and its first use. Restart with fresh local progress to test first-launch guidance. Open in-game help to review controls.
| Helper | Unlock | Free charges | Effect |
|---|
| Swap | 3 | 3 | Changes the loaded marble colour. |
| Depth Charge | 6 | 2 | Arms an area-blast shot; tap the tower after activation. Rocks and ice can be destroyed. |
| Prism | 10 | 2 | Arms a rainbow shot; fire it at the tower to choose the best matching colour. |
| Tide | 15 | 2 | Regroups lower colours into larger patches required by your goals. |
A guided first use can be free and is not the same as spending a normal saved charge. Exhausted charges may request a rewarded placement. Confirm success/cancel/failure callbacks and grant rewards exactly once. Test each helper with valid preconditions: armed shots need a subsequent shot, Undo needs an unmatched pick and Bucket needs occupied tray slots.
CHAPTER 06Visual editor: your first edited level
Open Szekipapa77 > Bubble Spire > Level Editor. Exact implementation: Editor/BubbleTowerLevelEditor.cs.
Edit Name, Pattern tag, First-time hint and Marbles (budget). Around (columns) supports 12–28 and Height (rows) supports 6–40. Paint the unwrapped tower using Marble, Frozen, Rock, Oyster or Erase. Select colours from the palette and set colour-goal quantities to achievable values. Pattern fill uses colours, break-up and seed.
- Back up levels.json and duplicate a simple early level.
- Make one small change and use a distinctive visible name/note where supported.
- Validate using the editor and fix every reported issue.
- Save explicitly, stop and restart Play mode and open the edited index.
- Check both supplied scenes and rebuild demos after data changes.
CHAPTER 07Generation, insertion and replacement
Count, Start at level #, Seed, Start/End difficulty and Breather every (0–8). Replace that range (else insert) removes the existing range before insertion; unchecked inserts and shifts later records. Start 26 appends to the original bank. Bot validation is part of generation.
Worked extension: keep a backup, generate five candidates after the original 25, use a moderate difficulty curve, validate every candidate, then Save. Reload JSON to confirm the count changed. Deliberately choose replacement only when you intend to overwrite those records. Runtime generated continuation does not write new project JSON assets. Solver/bot validation does not replace manual play testing.
CHAPTER 08Actual level schema
Canonical file: Assets/Games/BubbleTower/Resources/bubbletower/Levels/levels.json. Root levels array order defines visible level numbering; preserve version/type fields and numeric indices. The following is the actual first record, not a hypothetical schema:
{
"name": "First Light",
"pattern": "rings",
"cols": 16,
"rows": 10,
"cells": [
0,
3,
3,
5,
5,
5,
5,
0,
0,
0,
0,
5,
5,
5,
5,
0,
0,
0,
3,
0,
0,
0,
0,
0,
0,
0,
3,
0,
0,
0,
0,
0,
0,
0,
0,
5,
0,
0,
0,
0,
0,
0,
5,
0,
0,
0,
0,
0,
3,
0,
5,
0,
0,
0,
0,
0,
3,
5,
0,
0,
0,
0,
0,
0,
3,
3,
3,
3,
0,
0,
0,
3,
3,
3,
3,
3,
0,
0,
3,
3,
3,
3,
3,
5,
3,
0,
3,
3,
3,
3,
3,
3,
3,
5,
0,
3,
3,
3,
3,
0,
3,
3,
3,
3,
3,
3,
3,
3,
3,
5,
3,
5,
3,
5,
3,
5,
3,
0,
3,
5,
5,
5,
3,
0,
5,
5,
5,
3,
5,
5,
5,
5,
5,
5,
5,
5,
5,
5,
5,
0,
5,
5,
5,
3,
5,
5,
5,
5,
5,
5,
5,
5,
5,
5,
3,
0,
5,
5,
5,
0
],
"goalColor": [
0
],
"goalCount": [
25
],
"balls": 30,
"difficulty": 0.05,
"breather": false,
"tut": 1,
"hint": "",
"seed": 4237
}
Use the visual editor to create structured coordinates, arrays and indices. Keep JSON valid and preserve booleans/numbers. Validate through BubbleTowerLevelEditor.cs before playing. Do not rename resource IDs or save keys during a display-text change.
CHAPTER 09Worked reskin: interface colours
Open Scripts/BubbleTowerGame.Hud.cs, find Brass with current hex #F2B544. Stop Play mode and back up the file. Change only that value to #42C9B0, keep the symbol/type, allow compilation and restart the scene. Review HUD, buttons, helper captions, result/settings popups and landscape layout. For Guess Five, check Night and Day and maintain clear clue contrast. Editor preview palettes can be separate; change them deliberately if matching is needed.
Exact runtime palette/material assignments follow. These show which file and assignment owns a colour; editing an editor-only colour does not reskin the runtime. Rebuild after accepting the change.
| Exact source reference | Current assignment |
|---|
BtData.cs:80 | public static readonly string[] Hex = { "#FF5D6C", "#FFC63D", "#2FD6A3", "#3E8BFF", "#9C6BFF", "#EEF2F7" }; |
BubbleTowerGame.cs:28 | static readonly string[] BoostColor = { "#3E8BFF", "#2B3A67", "#9C6BFF", "#1FB5A6" }; |
BubbleTowerGame.cs:135 | SeaPopup(title, msg, HzUI.Hex("#1FB5A6"), Art(art), ("Got it", HzUI.Hex("#F2A93B"), () => closed = true)); |
BubbleTowerGame.cs:307 | var p = SeaPopup("Lighthouse lit!", $"Level {lv} cleared with {left} marble{(left == 1 ? "" : "s")} to spare.", HzUI.Hex("#F2A93B"), Art("win_badge"), |
BubbleTowerGame.cs:308 | ("Next Level", HzUI.Hex("#1FB5A6"), () => HzPlatform.Interstitial(GameId, () => LoadLevel(Level)))); |
BubbleTowerGame.cs:327 | SeaPopup("Out of marbles!", left + "\nGet 5 more marbles and keep going?", HzUI.Hex("#3E8BFF"), Art("lose_badge"), |
BubbleTowerGame.cs:328 | ("+5 Marbles (ad)", HzUI.Hex("#1FB5A6"), () => HzPlatform.Rewarded(GameId, "continue", ok => { if (ok) ContinueLevel(); else LoadLevel(Level, true); })), |
BubbleTowerGame.cs:329 | ("Retry", HzUI.Hex("#F2A93B"), () => LoadLevel(Level, true))); |
BubbleTowerGame.cs:331 | SeaPopup("Out of marbles!", left, HzUI.Hex("#3E8BFF"), Art("lose_badge"), |
BubbleTowerGame.cs:332 | ("Retry", HzUI.Hex("#F2A93B"), () => LoadLevel(Level, true))); |
BubbleTowerGame.cs:348 | Toast("+5 marbles!", HzUI.Hex("#FFE7A8"), 1.2f); |
BubbleTowerGame.cs:369 | ("Get 1 (ad)", HzUI.Hex("#1FB5A6"), () => HzPlatform.Rewarded(GameId, "booster_" + b, ok => { if (ok) { SetCharges(b, Charges(b) + 1); Toast("+1 " + BoostName[b]); HzAudio.Play(Sfx.Bonus, 0.7f); } })), |
BubbleTowerGame.cs:370 | ("No thanks", HzUI.Hex("#9AA6BF"), null)); |
BubbleTowerGame.Hud.cs:12 | static readonly Color Ink = HzUI.Hex("#1E2A4A"); |
BubbleTowerGame.Hud.cs:13 | static readonly Color Navy = HzUI.Hex("#24345F"); |
BubbleTowerGame.Hud.cs:14 | static readonly Color Brass = HzUI.Hex("#F2B544"); |
BubbleTowerGame.Hud.cs:15 | static readonly Color Cream = HzUI.Hex("#FFF7EA"); |
BubbleTowerGame.Hud.cs:135 | var chip = HzUI.Panel("Chip" + k, parent, HzUI.Hex("#EAF2FB"), 36).rectTransform; |
BubbleTowerGame.Hud.cs:148 | var check = HzIcons.Icon(chip, HzIcons.Check, 70, HzUI.Hex("#1FB5A6")); |
BubbleTowerGame.Hud.cs:163 | else { var c = HzUI.Img("Icon", card, HzUI.Circle(64), HzUI.Hex("#3E8BFF")); c.raycastTarget = false; c.rectTransform.Place(new Vector2(0, 0.5f), new Vector2(icon * 0.8f, icon * 0.8f), new Vector2(icon / 2 + 14, 8)); } |
BubbleTowerGame.Hud.cs:198 | var bd = HzUI.Panel("Badge", v.Root, HzUI.Hex("#FF5D6C"), 30); bd.raycastTarget = false; |
BubbleTowerGame.Hud.cs:216 | v.Inner.color = un ? HzUI.Hex(BoostColor[b]) : HzUI.Hex("#B9C2D6"); |
BubbleTowerGame.Hud.cs:222 | v.Badge.GetComponent<Image>().color = c > 0 ? HzUI.Hex("#FF5D6C") : HzUI.Hex("#1FB5A6"); |
BubbleTowerGame.Hud.cs:224 | v.Caption.color = un ? Ink : HzUI.Hex("#6F7C99"); |
BubbleTowerGame.Hud.cs:236 | _ballsText.color = _e.Balls <= 5 ? HzUI.Hex("#FFB4B4") : Color.white; |
BubbleTowerGame.Hud.cs:410 | Toast("Nice! " + BoostName[b] + " is yours", HzUI.Hex("#FFE7A8"), 1.3f, 0.3f); |
BubbleTowerGame.Hud.cs:471 | var inner = HzUI.Panel("Inner", card.transform, HzUI.Hex("#EAF6F7"), 44); inner.raycastTarget = false; |
BubbleTowerGame.Hud.cs:534 | s.AddOutline(on ? HzUI.Hex("#B07A1A") : new Color(0.5f, 0.55f, 0.65f), 4); |
BubbleTowerGame.Hud.cs:547 | ("Try it!", HzUI.Hex("#F2A93B"), done)); |
BubbleTowerGame.Hud.cs:554 | (SoundLabel(), HzUI.Hex("#3E8BFF"), null), |
BubbleTowerGame.Hud.cs:555 | (MusicLabel(), HzUI.Hex("#9C6BFF"), null), |
BubbleTowerGame.Hud.cs:556 | ("How to play", HzUI.Hex("#1FB5A6"), ShowHowTo), |
BubbleTowerGame.Hud.cs:557 | ("Restart Level", HzUI.Hex("#F2A93B"), () => LoadLevel(Level, true)), |
BubbleTowerGame.Hud.cs:558 | ("Close", HzUI.Hex("#6F7C99"), null)); |
BubbleTowerGame.Hud.cs:571 | HzUI.Hex("#1FB5A6"), Art("logo_lighthouse"), ("Got it", HzUI.Hex("#F2A93B"), null)); |
BubbleTowerGame.View.cs:56 | _cam.backgroundColor = HzUI.Hex("#F6C8A8"); |
BubbleTowerGame.View.cs:67 | _sun.color = HzUI.Hex("#FFD9B0"); _sun.intensity = 1.35f; |
BubbleTowerGame.View.cs:74 | fill.transform.rotation = Quaternion.Euler(20, 55, 0); fill.color = HzUI.Hex("#9FB8FF"); fill.intensity = 0.38f; |
BubbleTowerGame.View.cs:77 | rim.transform.rotation = Quaternion.Euler(12, 160, 0); rim.color = HzUI.Hex("#FFB48A"); rim.intensity = 0.7f; |
BubbleTowerGame.View.cs:80 | RenderSettings.ambientSkyColor = HzUI.Hex("#F3C9C0"); |
BubbleTowerGame.View.cs:81 | RenderSettings.ambientEquatorColor = HzUI.Hex("#D9A796"); |
BubbleTowerGame.View.cs:82 | RenderSettings.ambientGroundColor = HzUI.Hex("#3F6079"); |
BubbleTowerGame.View.cs:86 | RenderSettings.fogColor = HzUI.Hex("#F4C7A9"); |
BubbleTowerGame.View.cs:113 | _rockMat = Lit("#7D7A78", 0.18f); |
BubbleTowerGame.View.cs:115 | _oysterMat = Lit("#C9A4D8", 0.55f); |
BubbleTowerGame.View.cs:116 | _pearlMat = Lit("#FFF8F0", 0.97f); _pearlMat.SetColor("_EmissionColor", new Color(0.35f, 0.32f, 0.3f)); _pearlMat.EnableKeyword("_EMISSION"); |
BubbleTowerGame.View.cs:117 | _brassMat = Lit("#E3A93E", 0.82f); _brassMat.SetFloat("_Metallic", 0.75f); |
BubbleTowerGame.View.cs:118 | _bandMat = Lit("#3E8BFF", 0.7f); |
BubbleTowerGame.View.cs:119 | _stripeA = Lit("#F7F1E8", 0.35f); _stripeB = Lit("#E8505E", 0.35f); |
BubbleTowerGame.View.cs:120 | _capMat = Lit("#2B3A67", 0.5f); |
BubbleTowerGame.View.cs:122 | _roofMat = Lit("#C93F4F", 0.45f); |
BubbleTowerGame.View.cs:123 | _islandMat = Lit("#8C7363", 0.12f); |
BubbleTowerGame.View.cs:124 | _seaMat = Lit("#1A7F9C", 0.72f); _seaMat.SetColor("_EmissionColor", HzUI.Hex("#0A3550") * 0.25f); _seaMat.EnableKeyword("_EMISSION"); |
BubbleTowerGame.View.cs:125 | _bombMat = Lit("#232B45", 0.85f); _bombMat.SetFloat("_Metallic", 0.4f); |
BubbleTowerGame.View.cs:127 | _prismMat = Lit("#FFFFFF", 0.95f); _prismMat.EnableKeyword("_EMISSION"); |
BubbleTowerGame.View.cs:184 | var grad = HzUI.Img("Grad", go.transform, HzUI.Gradient(HzUI.Hex("#9FB6E8"), HzUI.Hex("#FFC9A3")), Color.white); |
BubbleTowerGame.View.cs:455 | var c = code >= 0 ? HzUI.Hex(Code.Hex[code]) : code == -2 ? HzUI.Hex("#2B3A67") : Color.white; |
BubbleTowerGame.View.cs:576 | Run(DelayedBanner(maxT, "Hot streak! +" + ev.Value + " marble", "#FF8A3D")); |
BubbleTowerGame.View.cs:579 | Run(DelayedBanner(maxT + 0.3f, "Pearl! +" + ev.Value + " marbles", "#C27BE0")); |
CHAPTER 10Worked reskin: visible text
Open Scripts/BubbleTowerGame.Hud.cs, find the displayed literal "marbles" and replace it with "shots". In HzUI.Label/Button, the first string can be the hierarchy object name; change the displayed text argument. Keep object identifiers, GameId, save/resource keys and asset filenames stable.
Preserve interpolation, numeric ToString formatting, line breaks and ON/OFF refresh logic. Update initial construction and later refresh assignments. Helper name/help sources are BoostName in BubbleTowerGame.cs and BoostDesc in BubbleTowerGame.cs. Level names and hints are separate JSON content. Visible title appears in the game Title property, HzGameInfo and sometimes separate screen lettering/art; review all branding deliberately.
There is no automatic translation of hard-coded game strings. A translation table must be called by each assignment. Word dictionaries/answer banks are gameplay data and do not translate Guess Five menus. Test title, tutorial, helper unlocks, HUD, settings/result popups and refreshed labels in both orientations.
| Exact source reference | Current assignment |
|---|
BubbleTowerGame.cs:25 | static readonly string[] BoostName = { "Swap", "Depth Charge", "Prism", "Tide" }; |
BubbleTowerGame.cs:135 | SeaPopup(title, msg, HzUI.Hex("#1FB5A6"), Art(art), ("Got it", HzUI.Hex("#F2A93B"), () => closed = true)); |
BubbleTowerGame.cs:307 | var p = SeaPopup("Lighthouse lit!", $"Level {lv} cleared with {left} marble{(left == 1 ? "" : "s")} to spare.", HzUI.Hex("#F2A93B"), Art("win_badge"), |
BubbleTowerGame.cs:327 | SeaPopup("Out of marbles!", left + "\nGet 5 more marbles and keep going?", HzUI.Hex("#3E8BFF"), Art("lose_badge"), |
BubbleTowerGame.cs:331 | SeaPopup("Out of marbles!", left, HzUI.Hex("#3E8BFF"), Art("lose_badge"), |
BubbleTowerGame.cs:368 | SeaPopup("Need a " + BoostName[b] + "?", "Watch a short ad to get one more.", HzUI.Hex(BoostColor[b]), Art(BoostArt[b]), |
BubbleTowerGame.cs:369 | ("Get 1 (ad)", HzUI.Hex("#1FB5A6"), () => HzPlatform.Rewarded(GameId, "booster_" + b, ok => { if (ok) { SetCharges(b, Charges(b) + 1); Toast("+1 " + BoostName[b]); HzAudio.Play(Sfx.Bonus, 0.7f); } })), |
BubbleTowerGame.Hud.cs:58 | _levelText = HzUI.Label("Lvl", lp.transform, "Level " + Level, 54, Color.white); |
BubbleTowerGame.Hud.cs:61 | _levelSub = HzUI.Label("Name", lp.transform, _def.name, 30, Brass, TextAnchor.MiddleCenter, false); |
BubbleTowerGame.Hud.cs:87 | var gt = HzUI.Label("GoalT", _goalPanel, "GOAL", 40, Ink); gt.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(240, 60), new Vector2(0, -12), new Vector2(0.5f, 1)); |
BubbleTowerGame.Hud.cs:109 | _tipText = HzUI.Label("T", _tipPanel, "", portrait ? 40 : 40, Ink); _tipText.rectTransform.Stretch(18); |
BubbleTowerGame.Hud.cs:145 | var t = HzUI.Label("N", chip, "", 50, Ink); |
BubbleTowerGame.Hud.cs:164 | _ballsText = HzUI.Label("N", card, "", (int)(size.y * 0.46f), Color.white); |
BubbleTowerGame.Hud.cs:167 | var cap = HzUI.Label("Cap", card, "marbles", 28, Brass, TextAnchor.MiddleCenter, false); cap.fontStyle = FontStyle.Bold; |
BubbleTowerGame.Hud.cs:177 | v.Btn = HzUI.Button("Booster" + b, parent, null, Color.white, Color.white, () => OnBooster(id), 40, 75); |
BubbleTowerGame.Hud.cs:201 | v.BadgeText = HzUI.Label("N", bd.transform, "", 36, Color.white); v.BadgeText.rectTransform.Stretch(); |
BubbleTowerGame.Hud.cs:203 | v.Caption = HzUI.Label("Cap", v.Root, "", 30, Ink); |
BubbleTowerGame.Hud.cs:221 | v.BadgeText.text = c > 0 ? c.ToString() : "+"; |
BubbleTowerGame.Hud.cs:223 | v.Caption.text = un ? BoostName[b] : "Lv " + BoostUnlock[b]; |
BubbleTowerGame.Hud.cs:253 | if (_levelText) _levelText.text = "Level " + Level; |
BubbleTowerGame.Hud.cs:272 | var t = HzUI.Label("L", bg.transform, "Level " + Level, 92, Color.white); t.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(760, 110), new Vector2(0, -16), new Vector2(0.5f, 1)); |
BubbleTowerGame.Hud.cs:275 | var n = HzUI.Label("N", bg.transform, sub, 44, Brass); n.rectTransform.Place(new Vector2(0.5f, 0), new Vector2(760, 70), new Vector2(0, 18), new Vector2(0.5f, 0)); |
BubbleTowerGame.Hud.cs:286 | var t = HzUI.Label("T", bg.transform, text, 56, Color.white); t.rectTransform.Stretch(8); t.AddOutline(new Color(0, 0, 0, 0.3f), 3); |
BubbleTowerGame.Hud.cs:294 | var l = HzUI.Label("Praise", _fx, word, 110, Brass); |
BubbleTowerGame.Hud.cs:410 | Toast("Nice! " + BoostName[b] + " is yours", HzUI.Hex("#FFE7A8"), 1.3f, 0.3f); |
BubbleTowerGame.Hud.cs:497 | var t = HzUI.Label("Title", ribbon.transform, title, 66, Color.white); t.rectTransform.Stretch(6); |
BubbleTowerGame.Hud.cs:502 | p.Message = HzUI.Label("Msg", card.transform, message, 42, Ink, TextAnchor.MiddleCenter, false); |
BubbleTowerGame.Hud.cs:546 | SeaPopup("New: " + BoostName[b] + "!", BoostDesc[b] + "\nYou get " + BoostStart[b] + " free!", HzUI.Hex(BoostColor[b]), Art(BoostArt[b]), |
BubbleTowerGame.Hud.cs:553 | var p = SeaPopup("Settings", "", Navy, null, |
BubbleTowerGame.Hud.cs:554 | (SoundLabel(), HzUI.Hex("#3E8BFF"), null), |
BubbleTowerGame.Hud.cs:555 | (MusicLabel(), HzUI.Hex("#9C6BFF"), null), |
BubbleTowerGame.Hud.cs:569 | var p = SeaPopup("How to play", |
BubbleTowerGame.Hud.cs:575 | static string SoundLabel() => HzAudio.Muted ? "Sound: OFF" : "Sound: ON"; |
BubbleTowerGame.Hud.cs:576 | static string MusicLabel() => HzAudio.MusicMuted ? "Music: OFF" : "Music: ON"; |
CHAPTER 11Artwork, fonts and audio
Game assets live in Resources/bubbletower. Replace images through Unity while retaining resource keys, .meta GUIDs, Sprite/UI importer settings, alpha, pivots and slicing. Procedural fallbacks live in the view/art sources and may need their own changes; replacing a PNG does not automatically alter fallback graphics.
The private Kit supplies fonts and UI helpers. Retain included font licence notices and verify glyph coverage for new languages. Music resource: harbor_loop.ogg. Preserve load keys when replacing clips, review compression/loop boundaries, and test Sound and Music toggles separately. Inspect the Sfx/HzAudio code for current sound bindings.
CHAPTER 12Saving and resets
HzSave wraps PlayerPrefs, coalesces writes and flushes on focus loss/quit. WebGL uses browser IndexedDB tied to the hosting origin. Clearing site data or using a new origin starts a new save; this is not cloud synchronization. GameId-prefixed keys and some JSON progress hold levels, stars, helper counts, tutorial flags, audio options and statistics where implemented.
Test fresh launch, a completed level, helper consumption, audio toggles, focus loss, quit and relaunch. Use a separate development profile for resets. Preserve existing save keys for a visual reskin; changing them discards continuity. Inspect calls to HzSave in the listed scripts before adding a custom migration.
CHAPTER 13Advertising setup and scope
Settings asset: Assets/Szekipapa77Ads/Resources/AdsSettings.asset. Inspect backend, useGoogleTestIds and missingSdkFallback through the publisher Ads editor. This buyer contains integration/adapter code, not external provider SDK binaries. Missing-provider fallback and WebGL ads are simulated, not live commercial impressions.
For native real ads, obtain the supported provider from its official source in your working project, configure your own app/placement IDs and test with test IDs. Verify banner/interstitial/rewarded success, cancel and failure. Before production, supply your own IDs and disable simulated missing-SDK fallback. The build tools reject unsafe production fallback settings. No purchases or remote backend is supplied.
CHAPTER 14Android release
Install matching Android support in Unity Hub. Select Android, use the mobile scene, run Setup and review application ID com.szekipapa77.bubbletower, version, orientation and signing. Build Android writes Builds/Android/bubbletower.apk. Configure your own keystore and distribution settings; a demo APK is not a signed production release.
A provider-free build simulates ads. A supplied test-provider demo is not evidence of production account configuration. Use a Build Profile for your distribution format. On real hardware test touch, safe areas, audio focus, restart and ad callbacks. Keep signing passwords/keystores/private service files outside buyer archives. Successful compilation is not a physical-device play test.
CHAPTER 15WebGL release and hosting
Install WebGL support, select the WebGL target and run Setup and Build WebGL. Output: Builds/WebGL/bubbletower/. Serve index.html and every Build/StreamingAssets payload over HTTPS, not file://. Match compression, MIME and Content-Encoding to the host. The shell template controls the outer page; scene selection controls game orientation.
The publisher demo uses HyperCodeStore. Upload actual loader/framework/data/WASM files, not just a link; include the TemplateData shell required by the host. Test in a fresh anonymous session: loading, real pointer/keyboard input, audio after interaction, save/reload and mobile sizing. Rebuild and re-upload every changed game artifact.
CHAPTER 16Troubleshooting
Compilation errors: restore matching packages and import the entire private Kit/Ads layer; keep only one game Kit. Pink or invisible objects: run Setup and inspect Graphics and every Quality URP assignment. No input: check classic input handling, canvas focus and blocking modal. Missing resources: restore exact keys/importers/GUIDs.
Edited level absent: stop Play, Save, reload the canonical JSON and check saved progress/play overrides. No real ads: provider SDK is intentionally absent; inspect installed adapter and fallback. Text reverts: change both build and refresh assignments from the reference table. Scene reload exception: retain owner-lifetime and generation checks on delayed callbacks; do not remove those guards.
CHAPTER 17Support and release checklist
Support: szekipapa77@gmail.com. Include game title, exact Unity version, platform, first Console error and reproduction steps; never send credentials.
- Pass a clean import and play both scenes.
- Play tutorial, normal win/failure and every helper activation/effect.
- Check reskin strings, settings, save/reload and both layouts.
- Rebuild Android/WebGL after code or data changes and verify final output bytes.
- Verify public APK, browser demo and full documentation links before publishing.
- Keep licences and exclude private tooling/capture/workflow material from customers.