HyperCasual LiveOps Kit
A source-only toolkit that covers the service layer of a mobile game so your team can stay focused on gameplay. Persistence, monetization, LiveOps retention loops, platform integrations, and UI feedback all arrive as decoupled modules that boot themselves, run with zero third-party SDKs installed, and swap to real vendor backends the moment you opt in.
Every module follows the same recipe: a plain-C# service, an interface where a backend can vary, a settings section edited from one editor window, and an in-engine stand-in backend so the whole kit works in the editor from the first minute.
Game Setup Assistant
Tools > HLK > Game Setup Assistant is the recommended starting point for a new developer. It edits the real runtime assets directly; it is not a demo or a second configuration copy. Use it for game content and balancing. Use Advanced Toolset for SDK providers, store credentials, AdMob IDs, consent, platform defines, and production checks.
| Assistant page | What a developer changes there | Runtime source of truth |
|---|---|---|
| Game & Levels | Menu/game scenes, visible and initially unlocked levels, win coins, multipliers, progress gifts, currency art | StarterMenuConfig.asset |
| Minigames | Wheel slices, slot symbols/payouts, chest drops, per-game cooldowns, chest keys | HLKSettings.Minigames + StarterMenuConfig.asset |
| Goals | Daily/weekly/event goals and permanent achievements, targets, events, rewards | QuestCatalog + QuestDefinition assets |
| Daily Gifts | Ordered streak days, reward amounts, special days, grace policy, rewarded doubling | StarterGiftSchedule + HLKSettings.StarterGifts |
| Economy & LiveOps | IAP products, soft-currency/cosmetic shop items, unlock conditions, offers, battle pass, energy, leaderboard and achievement IDs | Referenced catalog/config assets |
| Services | Ads, haptics, audio, native UI, sharing, rate-us, notifications, analytics, localization, cloud save, privacy, runtime options | Common sections of HLKSettings.asset |
| Validate | Scenes, data references, duplicate IDs, icons, build order, documentation, export boundary | Read-only project checks |
Recommended first setup
- Open the assistant and click Apply Missing Presets. It fills empty references only and never replaces an assigned asset.
- On Game & Levels, enter the menu and gameplay scene names and tune the initial level range. Click Add Both to Build Settings; the menu becomes build index 0.
- Edit minigame, goal, daily-gift, shop, and battle-pass assets inline. Use the Ping button whenever you want the normal Inspector or want to duplicate an asset.
- Open Validate and clear red items. Yellow items are recommendations or production integrations that may intentionally remain simulated during development.
Assets/HyperCasualLiveOpsKit/GameSetup/. This keeps the shipped preset
intact and gives the game its own portable data asset.Advanced module coverage
The Services page also shows the boundary between designer-safe fields and provider-heavy setup.
Ads Pro, IAP Pro, app updates, attribution, crash reporting, deep links, permissions, push messaging,
remote config, and web views remain in the Advanced Toolset. They are not omitted:
the assistant links to their full configuration pages and its automated coverage test verifies that
every public HLKSettings section belongs to exactly one of these two surfaces.
Useful editor and CI entry points
- Tools > HLK > Validate Game Setup opens the assistant directly on its validation report.
- Tools > HLK > Open Combined Documentation opens this file from Unity.
- Automated builds can run
-executeMethod HLK.StarterMenu.Editor.HLKGameSetupValidator.ValidateForCi. The command logs every finding and fails the process only for blocking errors; recommendations remain warnings.
Using the Backend in Another Game
The kit is intentionally portable. Export the following folder as the reusable backend and menu:
Assets/HyperCasualLiveOpsKit
Third-party SDK source and binaries belong outside that folder:
Assets/__DO_NOT_PUBLISH_VENDOR_SDKS
When importing into a new project:
- Import the HLK folder and let Unity compile using simulated backends.
- For a power-up-only integration, copy the complete
PowerUpsfolder, open Tools > HLK > Power-Ups > Setup & Catalog, and provide a smallIPowerUpEffectHandleradapter for each game-specific effect. - Open Game Setup Assistant, apply missing presets, and change both scene names.
- Duplicate/tune the catalogs in
GameSetup, then connect your gameplay events. - Only after the game-facing validation passes, import the vendor SDKs you need into the excluded folder and configure their matching Advanced Toolset tabs.
- Use test ads and fake/sandbox purchases until store console products and privacy flows are ready.
Getting Started
Requirements: Unity 6000.0+, TextMeshPro (com.unity.textmeshpro)
and uGUI (com.unity.ugui). UPM installs pull these in automatically; a raw
.unitypackage import expects both packages to already be present in the project.
From import to a running showcase in about five minutes:
- Import the package. Only the toolkit's own files are added — no registries, no manifest edits, no binaries. Everything compiles immediately because each module defaults to a self-contained in-engine backend.
- Open the demo. Load
Assets/HyperCasualLiveOpsKit/Demo/HLK_Showcase.unityand press Play. The dashboard exercises nearly every module live: earn coins, spin the wheel, trigger toasts, simulate purchases, and more. (Content Gating / Unlocks is setup-only — it has no demo panel.) - Open Game Setup Assistant. Go to Tools > HLK > Game Setup Assistant for levels, minigames, achievements, goals, daily gifts, shop data, battle pass, and validation. Open its Advanced Toolset only when configuring provider SDKs or platform services.
- Generate starter data. Trigger Tools > HLK > Generate Starter Config
Assets — it creates ready-made preset assets (IAP catalog, wheel slices, chest tiers, quest catalog,
battle pass season, and the rest) under
Assets/HyperCasualLiveOpsKit/Presets/, pre-wired intoHLKSettings. - Go native when ready. Out of the box the modules run in their safe Dummy / Simulated / In-Engine modes so the project builds and plays with no SDKs. To get real native behaviour on device, switch each module's mode on its Toolset tab — see Real native features on device below for exactly what each one costs (several need only a mode change and no account at all).
| Module | Set on the Toolset | Also needs |
|---|---|---|
| Haptics | Mode → Production (now the default) | Nothing — real Vibrator / CoreHaptics is built in |
| Native UI (alerts, action sheets, toasts) | Mode → Native | Nothing on Android; iOS needs the HLK_NATIVE_UI define |
| Sharing | Mode → Native | The HLK_NATIVE_SHARE define — the share bridge is bundled, no SDK |
| Permissions | Already Auto — real OS prompts on device | Nothing — the <uses-permission> entries are injected from the usage-description fields you fill in |
| Local Notifications | Mode → Production | Install com.unity.mobile.notifications (free Unity package — no account). The HLK_MOBILE_NOTIFICATIONS define switches on automatically once it resolves, and the package adds the POST_NOTIFICATIONS permission for Android 13+ itself. |
| Deep Links | Add your scheme(s) to CustomSchemes (and hosts to UniversalLinkHosts) | Nothing for custom schemes — the launcher <intent-filter> is injected into the manifest at build time. Universal / App Links additionally need an assetlinks.json hosted on your domain. |
| Ads, IAP, Push, Game Services, Cloud Save, Attribution, Remote Config, App Update, Rate Us, Crash Reporter | Mode → its live value + the vendor define | The vendor SDK and a real account/console (AdMob, Google Play, Firebase, AppsFlyer, …) |
RuntimeInitializeOnLoadMethod hooks before your first scene wakes up, in the editor and in
builds alike.Architecture Overview
The kit is organized as independent modules under Runtime/, each with the same internal
shape. Understanding the shape once means you understand every module:
- Service — a plain C# class holding the logic (e.g.
SaveService,AdService,QuestService). Testable without a scene. - Interface — the seam consumers depend on where behavior can vary
(e.g.
IAudioService,IGameServices). - Backend + factory — platform or vendor adapters behind the interface. A
*BackendFactorypicks one from the configured mode and the compiled defines; a dummy or simulated backend always exists, so nothing breaks without SDKs. - Bootstrap — a static class with a
RuntimeInitializeOnLoadMethodhook that builds the service from settings and registers it in the service registry. - Settings section — a serializable block inside
HLKSettings, drawn by the matching Toolset tab.
The persistent host
MonoHost is a hidden, scene-surviving MonoBehaviour created before the first scene loads.
It runs coroutines for headless services (MonoHost.Run), exposes per-frame events, and —
critically — provides MonoHost.Post(Action) so background SDK callbacks can hand work back
to the main thread.
Threading rules
MonoHost.Post(() => ...) before touching services,
scenes, or UI. Async methods in the kit (cloud save, consent, updates) already marshal their engine
work back for you — your continuation code should still assume main-thread context and avoid blocking
with .Wait() or .Result, which can deadlock the frame.
Results instead of exceptions
Fallible operations return HlkResult / HlkResult<T> — a success flag
plus a structured HlkError — rather than throwing across module boundaries. Check
result.Success, or use ValueOr(fallback) / TryGet(out value) on the
generic form.
Service Registry
HLK.Core.Services is a minimal type-keyed locator. Modules register one shared instance
per capability at bootstrap (usually keyed by interface); game code resolves it anywhere without wiring
references through the scene.
| Member | Purpose |
|---|---|
Register<T>(impl) | Store (or replace) the implementation for capability T. Registering by interface is the recommended pattern. |
Resolve<T>() | Fetch the implementation; throws InvalidOperationException when nothing is registered. |
TryResolve<T>(out impl) | Non-throwing fetch; returns false with default when absent. |
IsRegistered<T>() | Query without resolving. |
Unregister<T>() / Clear() | Remove one registration / drop everything (mainly for tests). |
Count | Number of live registrations. |
Registration keys you can rely on after boot include SaveService, IFlowService,
IAudioService, IHapticService, IWallet, IAPBridge,
AdService, IShopService, IOffersService, StarterGiftService,
WheelService, IQuestService, IBattlePassService, IEnergyService,
IGameServices, CloudSaveService, IConsentService, IPushService,
SharingService, WebViewService, IDeepLinkService, IRemoteConfig,
IAppUpdateService, IAttributionService, CrashReporterService,
INotificationService, IRateUsService, IPermissionsService,
IPopupService, IToastService, ILocalizationService,
IEntitlements, ITimeSource, and ICheatService.
Configuration Assets
All module configuration funnels into a single ScriptableObject, HLKSettings, which must
live at Assets/HyperCasualLiveOpsKit/Resources/HLK/HLKSettings.asset to be found at runtime
(Resources path HLK/HLKSettings). Access it in code through
HLKSettings.Instance — if the asset is missing, a transient, fully-defaulted instance is
served so nothing null-refs.
Sections
One serializable section per module: Ads, AdsPro, Analytics,
AppUpdate, Attribution, Audio, BattlePass,
CloudSave, Consent, CrashReporter, DeepLinks,
Energy, GameServices, General, Haptics, IAP,
IAPPro, Localization, Minigames, NativeUI,
Notifications, Offers, Permissions, Push,
Quests, RateUs, RemoteConfig, Sharing,
WebView, and StarterGifts.
Preset assets
Data-heavy modules reference standalone ScriptableObjects. The generator at
Tools > HLK > Generate Starter Config Assets creates a working set under
Assets/HyperCasualLiveOpsKit/Presets/ and assigns each into the right settings slot:
| Preset asset | Feeds |
|---|---|
IAPCatalogPreset.asset | IAP.CatalogAsset |
WheelSlicesPreset.asset | Minigames.WheelDatabase |
SlotMachinePreset.asset | Minigames.SlotsConfig |
ChestPreset.asset | Minigames.ChestsConfig |
StarterGiftsPreset.asset | StarterGifts.Schedule |
QuestCatalogPreset.asset | Quests.Catalog |
BattlePassPreset.asset | BattlePass.Config |
EnergyPreset.asset | Energy.Config |
OfferCatalogPreset.asset | Offers.Catalog |
Localization/ tables | Localization.TableSet |
TargetFrameRate,
NeverSleep, VerboseLogging) live in the General section and are applied by
ServiceBootstrap before the first scene. Edit them on the General tab of the
Toolset.Save System
A versioned, migration-aware persistence layer built around one consolidated record. Game state lives
in a single SaveBlob (currency balances, owned items, streak data, string flags) that is
serialized as JSON through a pluggable ISaveBackend — PlayerPrefsSaveBackend by
default.
| Member | Purpose |
|---|---|
Current | The live SaveBlob; loaded lazily on first access. |
Load() / Peek() | Read from the backend (Peek does not adopt the result). |
RequestSave() | Debounced write — multiple calls in the window collapse into one flush (default window: DefaultDebounceSeconds = 1.0f). |
FlushNow() | Immediate write; returns an HlkResult. |
DeleteAll() | Wipe stored data. |
Migrate(json) | Run the migration chain over a raw stored payload. |
Loaded / Saved | Events after a load resolves / a flush succeeds. |
AttachLifecycle() | Hook pause/quit so unsaved changes auto-flush. |
Schema versioning
The current schema version is SaveSchema.Current = 2. Older payloads pass through
SaveMigrator, which chains ISaveMigration steps
(Migration_V0_to_V1, Migration_V1_to_V2) until the record is current. Add your
own step when you change the blob's shape.
Storage keys are namespaced under the HLK_ prefix — the blob itself sits at
HLK_SAVE_V1 with a schema marker alongside it.
Scene Flow & Timing
Scene navigation with a built-in fade, plus the small timing primitives the rest of the kit leans on: serializable cooldowns, swappable clocks, and a lightweight tween helper.
Faded scene loads
SceneFadeService implements IFlowService, raises
TransitionStarted / TransitionCompleted, and reports
IsTransitioning while a fade-load-fade sequence runs. A SceneReference field
gives designers a drag-and-drop scene picker that survives renames.
Cooldowns
Time sources
ITimeSource abstracts "what time is it" so reward logic can resist device-clock cheating.
LocalTimeSource uses the device clock; ServerTimeSource anchors to a remote
clock. TimeSourceFactory registers the configured one as the shared ITimeSource.
Tweens
TweenRunner (in HLK.Core) animates floats, vectors, transforms, and
CanvasGroup alpha on the persistent host. The Easing library covers the
standard curve families — Sine, Quad, Cubic, Quart, Quint, Expo, Circ, plus Back, Elastic, and Bounce —
each with in / out / in-out variants selected via EaseType.
Audio Engine
Pooled one-shot SFX and crossfading music behind a tiny interface. Designers author a
SoundBank asset — named entries with a clip, category, volume, pitch, and optional pitch
jitter — and code addresses sounds by SoundId (implicitly convertible from string).
Portable game sound palette
The included AudioLibrary/Clips folder contains 45 clearly named WAV effects. Run
Tools > HLK > Audio > Install Game Sound Library after importing or replacing clips.
The installer builds HLKGameSoundBank, assigns it to settings, and configures a 32-voice
pool so fast repeated sounds—including the same sound—can overlap.
| Member | Purpose |
|---|---|
Play(id) / Play(id, volumeScale) | Fire a one-shot from the pool. |
PlayMusic(id, fadeSeconds) | Start or crossfade the music track (default fade comes from settings). |
StopMusic(fadeSeconds) / StopAll() | Fade out music / stop all one-shot SFX (music is untouched — use StopMusic for the track). |
IsMusicPlaying | True while a music track is audible. |
Mixing model
The final gain of a voice is the product of three sliders:
Master × Category × entry volume. Categories (Music, Sfx, UI, Voice,
Ambient) each have a volume and — for Master, Music, and Sfx — a mute toggle in settings.
AudioSettings.PoolSize controls how many pooled AudioSource components serve
one-shots. The included game palette installs 32 voices; keep at least 24 for dense currency/confetti
sequences.
DefaultSoundBank and tune the mixer on the
Audio tab (Tools > HLK > Open Toolset).Haptic Feedback
Vibration with a pattern library, expressed as duration/amplitude pairs that translate to Android
vibrator waveforms and iOS haptic calls. The static Haptics facade forwards to the
registered IHapticService.
Pattern library
HapticPatterns ships: Tick, LightTap, MediumTap,
HeavyThud, LongBuzz, DoubleTap, TripleTap,
Heartbeat, RampUp, RampDown, Success,
Error, CoinPickup, LevelComplete, GameOver, and
Explosion. HapticPattern assets let designers author their own patterns without
touching code — create one via Assets ▸ Create ▸ HLK ▸ Haptics ▸ Pattern, and a few
sample patterns ship under Presets/Haptics. (HapticClip is the runtime struct
these compile down to, not an authorable asset.)
Backends & modes
| Mode | Backend | Notes |
|---|---|---|
Disabled | NullHapticBackend | Everything no-ops. |
Dummy | EditorHapticsBackend | Logs simulated pulses in the editor. |
Production | AndroidHapticsBackend / IOSHapticsBackend | Real vibration; Android needs the VIBRATE permission (one-click button on the Haptics tab). |
Virtual Economy
A persisted wallet for the game's currencies. CurrencyId enumerates
Coins (soft) and Gems (premium); balances are stored by stable string key so
saved data survives refactors. EconomyService wires the wallet to persistence and registers
it as IWallet.
| Member | Purpose |
|---|---|
Credit / Debit | Adjust a balance; returns HlkResult<long> with the new amount. Debit fails cleanly on insufficient funds. |
CanAfford(currency, amount) | Pre-check without mutating. |
SetBalance / Reset | Direct writes (cheats, migrations). |
GetRecentChanges(count) | Ring-buffer transaction history (capacity 64 by default) with reasons. |
BalanceChanged | Event: (CurrencyId, long newBalance). |
UI companions: CurrencyHud binds a live balance readout, and CoinGatherEffect
flies collected coin sprites toward the HUD counter before the credit lands.
CurrencyCatalog asset. Create one via
Assets ▸ Create ▸ HLK ▸ Economy ▸ Currency Catalog and place it at
Assets/HyperCasualLiveOpsKit/Resources/HLK/CurrencyCatalog.asset; it is auto-loaded from that
Resources path (HLK/CurrencyCatalog) by the HUD and icon resolver. It is not
generated by default — until you author one, the wallet still works but has no per-currency icons/names.Localization
Key-to-string translation with per-locale tables. A LocalizationTableSet groups one
LocalizationTable per locale code; LocalizationService picks the active locale
(persisted across sessions under HLK.Locale) and falls back gracefully — missing keys return
the supplied fallback or the key itself instead of blowing up.
| Member | Purpose |
|---|---|
Get(key, fallback) / TryGet(key, out value) | Translate a key in the active locale, consulting the fallback table when absent. |
SetLocale(locale) | Switch language at runtime; persists the choice. |
CurrentLocale / AvailableLocales | Active code and everything the table set covers. |
LocaleChanged | Event fired with the new locale code. |
For scene text, add the LocalizedText component next to a label: it looks up its key on
enable and re-resolves automatically when the locale changes. Sample tables for
en and fr ship with the kit, and the preset generator can scaffold a table set
under Presets/Localization/.
DefaultLocale (default
"en") on the Localization tab (Tools > HLK > Open
Toolset).In-App Purchases
Store-neutral purchasing through IAPBridge. Products are authored as
IAPProduct assets collected into an IAPCatalog; each product carries a
PurchaseReward hook that grants its contents on purchase, restore, or coupon redemption —
one grant path for all three.
| Member | Purpose |
|---|---|
InitializeAsync() | Bring the store backend up against the catalog; returns false instead of throwing when the store cannot become ready. |
PurchaseAsync(productId) | Full flow: visibility window check → store purchase → optional validation → reward grant → funnel analytics. |
RestoreAsync() | Re-grant non-consumables and subscriptions; consumables are never restored. |
GetLocalizedPrice(productId) | Store-localized price string with a configurable fallback. |
RedeemCouponAsync(code) | Single-use promo code → mapped product grant. |
Purchased / PurchaseFailed | Events after any grant / any failure with its normalized reason. |
Product kinds & failure reasons
IAPProductKind: Consumable, NonConsumable,
Subscription. Failures normalize to PurchaseFailureReason:
UserCancelled, UnknownProduct, AlreadyOwned,
PaymentDeclined, StoreNotReady, and more.
Backends
| Mode | Backend | Requires |
|---|---|---|
Dummy | DummyStoreBackend | Nothing. Simulated outcome (AlwaysSucceed / AlwaysFail / AlwaysCancel / Random), configurable latency (default 400 ms), optional fake store dialog in the editor. |
Test / Production | UnityPurchasingBackend | Unity IAP package + HLK_UNITY_PURCHASING |
Purchases Pro
The advanced purchasing layer on top of the base flow: subscription tiers, receipt validation,
limited-time visibility, and coupon mappings, all configured in the IAPPro settings
section.
Subscriptions
Tiers are declared as SubscriptionTier rows (product id, tier name, rank); a higher rank
satisfies checks for lower ones. SubscriptionGraceDays (default 3) keeps perks alive briefly
past expiry, and ReverifyOnAppStart re-checks receipts each launch.
GetSubscriptionOfferText(productId) surfaces free-trial / introductory / promotional /
win-back marketing copy from the product's SubscriptionOffers.
Receipt validation
Enable EnableServerValidation and point ValidatorEndpoint at your server.
ValidationFailurePolicy decides what an unverifiable purchase does:
Block (withhold the grant), GrantOptimistically, or Defer
(queue and re-check via ReverifyQueuedAsync()).
Limited-time products & coupons
LimitedTimeProducts constrain when a product may be bought —
IsWithinVisibilityWindow(productId) tells the shop UI whether to show it, and
NotifyOfferSeen(productId) records an impression. Coupons map redeem codes to
products for RedeemCouponAsync; each code redeems once per device.
Ads & Mediation
One ad API across every network. AdService resolves an IAdBackend from the
configured mediation provider, checks pacing and the ad-free entitlement before every intrusive show,
and normalizes the outcome into AdShowResult / RewardedResult.
Placements
AdPlacement is a ScriptableObject naming one ad slot: an id, an AdUnitKind
(Rewarded, Interstitial, Banner, AppOpen,
RewardedInterstitial, Native), and optional per-placement pacing overrides
(min interval, session/day caps, quiet-first-levels). Rewarded placements can opt out of the ad-free
entitlement via IgnoreNoAdsEntitlement — players who paid to remove ads usually still want
to choose rewarded ones.
Outcomes
AdShowOutcome | Meaning |
|---|---|
Completed | Watched to the end — grant the reward. |
Skipped | Dismissed early; the ad was shown but no reward is due. |
Suppressed | Blocked before display (pacing rule or ad-free entitlement) — see Reason. |
Failed | Load or show error. |
NotSupported | The active backend cannot serve this format. |
Providers
MediationProvider | Backend | Define |
|---|---|---|
AdMobDirect | AdMobBackend | HLK_ADMOB |
UnityLevelPlay | LevelPlayBackend | HLK_LEVELPLAY |
AppLovinMax | AppLovinBackend | HLK_APPLOVIN_MAX |
UnityAdsDirect | UnityAdsBackend | HLK_UNITY_ADS |
AdMode selects Dummy (the MockAdBackend renders a fake ad
overlay in-engine — no SDK at all), Test (real SDK, test units), or
Production. AdsDiagnosticsOverlay can be toggled in development builds to
watch fill, pacing decisions, and revenue callbacks live.
Ads Pro & Ad-Free
Extended formats, global frequency capping, and the paid ad-removal path, configured in the
AdsPro settings section.
Extended formats
Opt-in toggles enable App-Open ads (with a minimum interval, first-run suppression, and optional
on-resume showing), rewarded interstitials, and native ads — each with per-platform test/production unit
ids. Preload switches (PreloadRewardedOnStart, PreloadInterstitialOnStart,
ReloadAfterShow) keep inventory warm.
Frequency capping
AdPacing enforces the global rules before any intrusive ad shows:
InterstitialMinIntervalSec (default 60), InterstitialMaxPerSession and
InterstitialMaxPerDay (0 = uncapped), NoInterstitialsBeforeLevel, and a boot
grace window. Feed it progress via Pacing.SetLevel(n) / AdvanceLevel(). A
blocked show resolves as Suppressed with the failing rule in Reason.
Ad-free entitlement
The entitlement flag defaults to the key hce.entitlement.ad_free
(AdFreeEntitlementKey). Three switches decide what it silences:
AdFreeHidesBanners, AdFreeHidesInterstitials, and
AdFreeHidesAppOpen — all on by default. Rewarded ads keep working unless a placement says
otherwise. Impression-revenue forwarding (EnableImpressionRevenue) publishes paid events
through AdRevenueHub for attribution and analytics.
Shop & Cosmetics
A catalog-driven store for currency-priced, ad-unlocked, IAP-linked, gated, and cooldown-gift items, with an equip system that applies cosmetic effects to scene objects.
Item anatomy
A ShopItem asset combines an id, display info, a ShopItemKind
(NonConsumable / Consumable), and exactly one acquisition style:
- Currency-priced — a
CurrencyIdplusPrice; the wallet is debited. - Rewarded-ad unlock —
UnlockByRewardedAd; granted after a completed rewarded view. - Real-money —
IapProductIdlinks the item to an IAP product. - Cooldown gift —
ClaimCooldownSeconds > 0; claimable for free on a timer (GetCooldownRemainingfeeds the countdown label). - Gated —
PrerequisiteIdrequires owning another item first.
Cosmetics
Equippable items reference a CosmeticEffect: MaterialSwapEffect,
MeshSwapEffect, or ChildToggleEffect. Equipping is exclusive per
EquipTarget, and ShopItemView renders one catalog entry with its price, state,
and buttons. Ownership persists through PlayerPrefsShopStore.
ShopCatalog asset referenced by
your scene's ShopCatalogProvider; minigame-adjacent data lives on the
Mini-Games tab (Tools > HLK > Open Toolset).Timed Offers
Limited-window promotions that appear for the right player at the right moment. Offers are
OfferDefinition assets in an OfferCatalog; the service tracks eligibility,
impressions, and lifetime caps per offer.
Offer kinds
OfferKind | Trigger |
|---|---|
FirstSessions | Active between session numbers FirstSessionStart and FirstSessionEnd (feed the count via SetSessionCount). |
TimedWindow | Active between two UTC timestamps (WindowStartUtc / WindowEndUtc). |
EventTriggered | Armed for ArmWindowSeconds after a named gameplay event (e.g. level_fail) arrives via NotifyEvent. |
Always | Always a candidate — reads none of the trigger fields; only MaxLifetimeShows and CooldownSeconds gate when it surfaces. |
Presentation fields cover discount display: marketing price tags, an original-price strikethrough,
DiscountPercent, banner art, and an accent color. MaxLifetimeShows (default 3)
and CooldownSeconds throttle repeats; state persists per offer and can be cleared with
ResetState / ResetAllState during QA.
Daily Rewards
A login-streak reward loop ("starter gifts"): the player claims one gift per UTC day from a
designer-authored StarterGiftSchedule, streaks advance the ramp, and a rewarded ad can
multiply the claim.
Streaks & grace
Claiming on consecutive UTC days grows CurrentStreak. Missing days is judged against
StarterGifts.GraceDays: a gap is forgiven while the number of fully-missed days stays at or
below the grace allowance; one more and the streak resets to zero so the next claim restarts at day 1.
The default grace is 0 — strictly consecutive. Re-entering the app on the same day is
always a no-op.
Stock schedule
The generated preset is a looping 5-day cycle (LoopAfterLastDay = true):
| Day | Gift | Label |
|---|---|---|
| 1 | 150 Coins | Welcome Bonus |
| 2 | 275 Coins | Warming Up |
| 3 | 12 Gems | Gem Drop |
| 4 | 450 Coins | Almost There |
| 5 | 35 Gems (special) | Streak Finale |
Boosted claims & UI
ClaimBoosted(multiplier) multiplies currency amounts; ClaimWithAdBoost runs
an IRewardedAdGate first (wired to the DoubleAdPlacement setting) and claims
only on completion. StarterGiftPanel, StarterGiftTile, and
StarterGiftCountdown render the calendar — each tile reflects a GiftDayState of
Claimed, Today, or Locked. Preview helpers
(PreviewCurrent, PreviewDay, PreviewUpcoming) never mutate
state.
Clock hardening
DailyCooldownService offers a simpler stamped-cooldown variant with a paid retry
(TryPaidRetry). To resist device-clock rollback, set a TimeProbeUrl:
RemoteHttpTimeSource estimates server UTC from a single HTTP
HEAD request's Date header — no payload, no identifiers. Leave the field
empty to rely on the device clock only.
Reward Wheel
A weighted prize wheel split into headless selection and cosmetic spin. The outcome is decided by weight before the wheel ever moves; the animation then lands on the pre-selected slice, so presentation can never desync from the grant.
Slice data
A WheelSliceDatabase asset holds the slices. Each WheelSlice carries
RewardId, Amount, Weight (relative probability; 0 or negative
excludes the slice), icon, label, an optional localization key, and a wedge color. Selection uses
WeightedSelector; SampleDistribution(n) returns an empirical histogram so you
can sanity-check drop rates, and UseFixedSeed(seed) makes outcomes reproducible for
tests.
Spin presentation
WheelSpinSettings.Default spins for 3.2 seconds over 5 full turns with a cubic-out
deceleration. WheelSpinner.ComputeTargetAngle converts the winning index into a final
rotation (with jitter inside the wedge), and SegmentTicker raises a Tick event
each time the pointer crosses a slice boundary — hook it to a click sound and a light haptic for a
convincing ratchet. WheelView builds the wedge visuals from the database.
Chest & Slot Minigames
Two compact reward minigames sharing one RNG and reward-granting core (MinigameRng,
WeightedPicker, MinigameRewardGranter). Both are headless services you can
drive from any UI.
Chests
ChestConfig defines the tier list (each tier has an id, display name, key cost, and a
weighted drop table) plus ChestsToShowAtOnce (default 3) and whether duplicate tiers may
appear on one board. For animated reveals, split the flow: RollDrop decides the prize,
your animation plays, then GrantRolled pays it out.
Slot machine
SlotMachineConfig defaults: 3 reels, a spin cost of 10 coins, a 1.2-second
spin with 0.3-second reel stagger. Symbols are weighted; payouts map symbol combinations to rewards.
Validate(problems) on either config lists authoring mistakes before they reach players.
Quests
Windowed goal tracking driven by the same event names you already emit for analytics. Quests are
QuestDefinition assets in a QuestCatalog; the service scores incoming events
against them, tracks per-window progress, and pays currency on claim.
Windows & matching
QuestKind scopes a quest to a window: Daily, Weekly,
Event, or Permanent. When the window rolls over (UTC), progress resets
automatically. A definition matches by EventName, counts either occurrences or a numeric
event property (QuestMatchKind + PropertyName), can require a property filter
(ConditionPropertyName / ConditionValue), and finishes at Goal.
Rewards are a CurrencyId + RewardAmount.
Advance(questId, amount) progresses a quest directly when you'd rather not go through
events. Progress events: ProgressChanged, Completed, Claimed,
and a coarse Changed.
Battle Pass
A seasonal XP ladder with parallel free and premium reward tracks. XP arrives from gameplay events or direct grants; premium unlocks through an IAP product; every tier holds up to two claimable rewards.
Season configuration
BattlePassConfig holds the SeasonId (changing it starts a fresh season),
the tier ladder (each BattlePassTier sets an XP threshold and a reward per track), the
premium product id, and the XP feed: a CSV of qualifying event names (or
AcceptAllEvents), with XpSource choosing between a flat
XpPerEvent and reading a numeric event parameter. Reward kinds cover currency and item
grants — item fulfillment routes through your IBattlePassItemFulfiller.
Events: XpChanged, TierAdvanced, RewardClaimed,
ItemRewardGranted, PremiumUnlocked, Changed.
Wiring it up
- Unlock premium on purchase. Nothing unlocks the premium track automatically —
when the IAP for
Config.PremiumProductIdsucceeds, callServices.Resolve<IBattlePassService>().UnlockPremium()from your purchase-success handler (and again after a restore). - Currency rewards need an economy adapter. Depositing currency-kind rewards routes
through an
IBattlePassEconomyregistered in the service registry. A wallet-backed adapter ships at bootstrap whenever anIWalletis present; if you swap the wallet out, register your ownIBattlePassEconomyor currency rewards silently no-op. - XP from gameplay events. A bus→
IngestEventforwarder ships at bootstrap, so events you emit on the analytics bus that match the season'sEventNamesCsv(orAcceptAllEvents) drive XP. You can still callGrantXp/IngestEventdirectly.
Energy System
A regenerating lives/energy meter that gates level attempts, refills over wall-clock time (including while the app is closed), and sells refills through ads or IAP.
Defaults
Field (EnergyConfig) | Default |
|---|---|
Max / Start | 5 / 5 |
RegenSeconds | 480 (one unit per 8 minutes) |
CostPerLevel | 1 |
FullRefillProductId | energy_full_refill |
RewardedAdRefillAmount | 3 |
Reconcile() recomputes regeneration against elapsed real time — the
EnergyFocusWatcher calls it on app focus when RecalcRegenOnFocus is enabled,
so returning players find the hearts they earned while away. Regeneration stops at Max,
and every top-up path is clamped to Max as well — Refill / RefillToMax
discard any excess and return the number of units actually added.
Content Gating
Declarative lock/unlock rules for buttons, levels, and features. Designers author
UnlockCondition assets, attach an UnlockGate component to the UI, and the gate
keeps the target's interactable state, label, and locked overlay in sync.
Condition types
| Asset | Unlocks when… |
|---|---|
CurrencyUnlockCondition | the wallet holds enough of a currency. It only checks affordability and never debits — performing the actual spend is the game's job. |
LevelUnlockCondition | the player level reaches a threshold. |
FlagUnlockCondition | a named boolean flag is set (tutorials, one-time unlocks). |
CooldownUnlockCondition | a timed cooldown has elapsed; consuming re-stamps it. |
CompositeUnlockCondition | all / any of a list of child conditions pass. |
UnlockGate re-evaluates on a timer (default every 0.5 s) and raises
StatusChanged. With ConsumeOnClick enabled, clicking the unlocked target runs
the condition's consume step. Only stateful conditions mutate here: a CooldownUnlockCondition
re-stamps its timer on consume. Currency conditions do not spend on consume — they only check
affordability, so your game must perform the actual wallet debit itself.
Player state flows through an IPlayerContext. The built-in DefaultPlayerContext
bridges the wallet and save flags automatically, but the level number is not auto-wired
— with no level provider it reports 0, so a LevelUnlockCondition stays locked
(unless its threshold is 0). Supply a level provider, or register your own context, before level gates work:
Rate-Us Prompts
Store review requests with strict throttling, an optional soft pre-prompt, and platform-correct backends. The service tracks sessions and prompt history so review dialogs appear rarely and only to players who have had time to enjoy the game.
Throttle gates
| Setting | Default | Effect |
|---|---|---|
SessionsBeforeFirstPrompt | 5 | Never ask before this many sessions. |
DaysBetweenPrompts | 14 | Cooldown after any prompt. |
LifetimePromptCap | 3 | Hard ceiling per install. |
StopAfterRated | true | Once rated, never ask again. |
Soft pre-prompt
When PrePromptEnabled is on (default), a friendly in-game question runs before the real
store dialog, protecting your quota from players likely to decline. The default copy — title
"Having fun so far?", body "Would you mind rating us?", buttons "Rate it" /
"Maybe later" — is fully editable in settings. Provide the dialog UI by assigning a
RateUsSoftPrompter delegate via SetSoftPrompter — for example route it through
the Popup Stack.
Backends
RateUsMode: Disabled, Dummy (logs, no dialog), or
Production — Android uses Google Play In-App Review, iOS uses the system review controller, and
GetGateState() plus ResetGates() support QA. Fill in
AndroidPackageName / IOSAppId for the store-page fallback link.
The Android backend (AndroidRateUsBackend) reaches the Play In-App Review library entirely
by reflection, so no scripting define or managed reference is needed to compile. To make the native stars
popup actually fire on device, add the Play review Gradle dependency
com.google.android.play:review:2.0.1 — the Rate Prompt tab has a one-click
button that inserts it into your Android mainTemplate.gradle. Without that dependency the
request reports "unavailable" at runtime and the service falls back to the Play Store listing.
Local Notifications
Schedule on-device reminders — energy refilled, gift ready, streak about to break — with permission handling on both mobile platforms and a log-only backend everywhere else.
| Member | Purpose |
|---|---|
RequestPermissionAsync() | Prompt for authorization; resolves to a NotificationPermissionStatus. |
Schedule(request) / Schedule(title, body, fireAfter) | Queue a one-shot or repeating (Once/Hourly/Daily/Weekly) notification; returns a cancellable NotificationHandle. |
Cancel(handle) / CancelAll() | Remove pending notifications; stale handles no-op. |
GetPending() | Diagnostic snapshot of everything still scheduled. |
NotificationMode: Disabled, Dummy (log-only; the editor
default), or Production — the mobile backend requires Unity's Mobile Notifications package
and HLK_MOBILE_NOTIFICATIONS. Only the default hce_default
Android channel is created automatically; there is no settings list of extra channels — supply a custom
category/channel per notification via NotificationRequest.CategoryId. The iOS
alert/badge/sound options do come from settings.
Game Services
Leaderboards, achievements, and platform sign-in behind one interface. Your code speaks in
logical ids; a GameServicesCatalog asset maps each one to its Game Center and
Play Games store ids, so gameplay code never branches per platform.
| Member | Purpose |
|---|---|
SignInAsync() / SignOutAsync() | Authenticate the local player; SignInChanged fires with the player info. |
SubmitScoreAsync(id, score) | Post to a logical leaderboard; optionally mirrored to analytics. |
LoadTopScoresAsync(id, count) | Fetch entries for a custom leaderboard UI. |
UnlockAchievementAsync / IncrementAchievementAsync | Binary and percentage-based achievements. |
ShowLeaderboardUIAsync / ShowAchievementsUIAsync | Open the platform overlay. |
LeaderboardScoreFormatter renders scores per the catalog's
LeaderboardScoreFormat (Number, TimeCentiseconds,
Currency).
Resources/HLK/ path — the bootstrap loads it from Resources.Load("HLK/GameServicesCatalog").
If the asset lives anywhere else it is never found, and your logical ids are submitted to the platform
verbatim (the per-platform Game Center / Play Games id mapping never applies).Providers
GameServicesProvider | Backend | Define |
|---|---|---|
Auto | Picks per platform | — |
Dummy | DummyGameServicesBackend (in-memory) | — |
GameCenter | GameCenterBackend | HLK_GAME_CENTER |
PlayGames | PlayGamesBackend | HLK_PLAY_GAMES_SERVICES |
Cloud Save
Synchronizes the local save blob with a per-player cloud slot, with an explicit, configurable conflict policy instead of silent last-writer-wins.
Conflict policies
ConflictPolicy | Winner when both sides changed |
|---|---|
PlayerWins | The payload with the better player progress. |
SchemaWins | The payload with the newer save schema / timestamp. |
ForceLocal / ForceRemote | Always this device / always the cloud copy. |
CloudSaveAutoSync can sync on app pause (default on) and on currency changes (default
off). Results carry a CloudSaveStatus — Ok, ConflictResolved,
ConflictUnresolved, NotSignedIn, NetworkError,
QuotaExceeded — plus which SyncDirection actually happened.
Providers
CloudSaveProvider | Backend | Define |
|---|---|---|
Auto | Platform pick | — |
LocalFallback | LocalFileCloudBackend — a file-based stand-in for editor testing | — |
ICloud | iCloud key-value storage | HLK_ICLOUD |
PlayGames | PlayGamesCloudBackend (Saved Games) | HLK_PLAY_GAMES_SAVE |
Consent & Privacy
Privacy consent for two dimensions at once: regional regulation (GDPR-style consent via Google's UMP)
and Apple's App Tracking Transparency. The rest of the kit asks one question —
CanRequestPersonalized — and the consent module answers it.
| Member | Purpose |
|---|---|
RequestAsync() | Run the full flow: gather requirement info, show the form when regulation demands it, resolve. |
WaitForResolvedAsync() | Await the outcome from elsewhere (e.g. the ads bootstrap holds initialization on this). |
ShowPrivacyOptionsAsync() | Re-open the privacy options form on demand. |
ResetAsync() | Clear stored consent (QA / region simulation). |
CanRequestPersonalized | The single flag ad and analytics code should consult. |
ConsentMode: Disabled, Dummy (returns the forced
DummyOutcome from settings — handy for QA), or Production using Google UMP
behind HLK_UMP; on iOS the ATT prompt is additionally gated by
HLK_ATT. ConsentGate lets other bootstraps defer work
until consent resolves.
NSUserTrackingUsageDescription string to
Info.plist — Apple rejects builds that call the tracking API without it — and (2) install the
iOS 14 Advertising Support package (com.unity.ads.ios-support) and enable
HLK_ATT (the package auto-defines it). Without both, the ATT prompt is
skipped and personalized-ads eligibility falls back to non-tracked.Push Messaging
Remote push notifications with token lifecycle, topic subscriptions, and an in-editor simulator so you can build the receiving side without a backend.
| Member | Purpose |
|---|---|
InitializeAsync() | Start the backend and obtain a device token. |
SubscribeAsync / UnsubscribeAsync | Topic management; settings can auto-subscribe a topic list after registration. |
Token / HasToken / ClearTokenAsync() | Token access and invalidation. |
MessageReceived | Raised on the main thread for foreground/opened messages. |
SimulateIncoming(...) | Inject a fake PushMessage in Dummy mode. |
PushMode: Disabled, Dummy, or Firebase — the FCM
backend compiles behind HLK_FIREBASE_MESSAGING and requires the Firebase
Messaging package plus your platform config files.
In-App Web View
Open web content — privacy policy, support portal, seasonal event page — inside the app instead of bouncing players to an external browser. Supports raw HTML as well as URLs, with a JS message bridge back into the game.
| Member | Purpose |
|---|---|
OpenUrlAsync / OpenHtmlAsync / OpenAsync(request) | Show content with a chosen WebViewLayout; requests can restrict navigable schemes. |
OpenPrivacyPolicyAsync / OpenTermsAsync | One-liners for the two URLs every store review asks about. |
SendMessageAsync(msg) | Game → page messaging; MessageReceived is the reverse channel. |
PageStarted / PageFinished / Closed | Navigation lifecycle events. |
WebViewMode: Dummy, Disabled, or Native.
Dummy is a system-browser fallback with an emulated lifecycle — URLs (and HTML,
written to a temp file://) are handed to the OS browser via Application.OpenURL
and the "view" reports synthetic page-started/finished/closed events; it is not an in-engine panel.
Native binds to the UniWebView plugin. To use it:
import the UniWebView asset into the project, enable the
HLK_NATIVE_WEBVIEW scripting define, and ensure
HLK.Runtime.asmdef references the UniWebView assembly (this reference already ships
in the kit's asmdef).
Deep Links
Routes custom-scheme and universal links into structured payloads. Links that arrive before your handler subscribes are buffered, so cold-start links are never lost.
| Member | Purpose |
|---|---|
Received | Event with a parsed DeepLinkPayload (scheme, host, path, query map). |
DrainPending() / PendingCount | Deliver links buffered before a subscriber existed. |
Last / LastRaw | Most recent payload / raw URL for diagnostics. |
SimulateIncoming(url) | Feed a fake link through the full parse-and-dispatch path. |
Declare your schemes and App/Universal Link hosts in settings (CustomSchemes,
UniversalLinkHosts); the engine backend hooks Unity's application deep-link events on
device.
IPostGenerateGradleAndroidProject post-processor injects the launcher
<intent-filter> into the generated AndroidManifest.xml — a custom-scheme
filter for each entry in CustomSchemes (works with no account or server) and an
autoVerify https filter for each UniversalLinkHosts entry (App Links
additionally require an assetlinks.json hosted at your domain's
/.well-known/). Nothing is written when both lists are empty. Test a scheme on device with
adb shell am start -a android.intent.action.VIEW -d "yourscheme://offer?id=x" your.package.Remote Config
Server-tunable values with typed accessors and offline-safe defaults. Seed key/value defaults in settings; they answer immediately and act as the fallback whenever a fetch fails or a key is missing.
| Member | Purpose |
|---|---|
FetchAsync(force) / ActivateAsync() / FetchAndActivateAsync() | Two-phase fetch-then-apply, or the combined call. Fetches respect CacheSeconds (default 3600) unless forced. |
GetString/Bool/Int/Long/Double/Float | Typed reads with per-call fallbacks; values coerce sensibly across types. |
GetJson<T>(key, fallback) | Parse a JSON payload stored under one key straight into your own type. |
State / LastFetchTimeUtc / Keys | Lifecycle (DefaultsReady → Fetching → Activated / FetchFailed) and diagnostics. |
Providers
RemoteConfigProvider | Backend | Define |
|---|---|---|
Dummy | Defaults only — fully offline | — |
Firebase | Firebase Remote Config | HLK_FIREBASE_REMOTE_CONFIG |
UnityGamingServices | UGS Remote Config | HLK_UGS_REMOTE_CONFIG |
The backend factory preflights each provider and refuses to promote past Dummy until its prerequisites are met:
- Firebase — the Firebase Unity SDK (Remote Config), the platform config files
(
google-services.jsonon Android,GoogleService-Info.pliston iOS), and the HLK_FIREBASE_REMOTE_CONFIG define.HLK.Runtime.asmdefreferences theFirebase.RemoteConfigassembly. - Unity Gaming Services — the project linked to a UGS org, both the
com.unity.remote-configandcom.unity.services.authenticationpackages (UGS Remote Config signs in anonymously first), and the HLK_UGS_REMOTE_CONFIG define.HLK.Runtime.asmdefreferences theUnity.Services.RemoteConfigandUnity.Services.Authenticationassemblies.
App Updates
Detect newer builds and walk players through updating — via Google's in-app update flows on Android, a store version check on iOS, and a simulated backend for QA everywhere.
| Member | Purpose |
|---|---|
CheckForUpdateAsync() | Resolve an UpdateInfo (availability, version, staleness); CheckCompleted mirrors it as an event. |
StartFlexibleUpdateAsync() / StartImmediateUpdateAsync() | Background download vs. blocking full-screen flow. |
CompleteFlexibleUpdateAsync() | Apply/finalize a downloaded flexible update (Play completeUpdate). |
OpenStorePageAsync() | Store-listing fallback via StoreDeepLink. |
SimulateAvailability(state) | Force a state in the simulated backend for QA. |
AppUpdateMode: Disabled, Simulated (settings choose the forced
availability and version), AppStoreVersionCheck (queries Apple's public iTunes Lookup
endpoint and deep-links to the store listing — no SDK needed), or PlayInApp behind
HLK_PLAY_APP_UPDATE. UpdateAvailability.CriticalUpdate
supports force-update gates; VersionComparer handles semantic version ordering.
AppStoreVersionCheck reads the numeric
App Store id from the Rate Us settings section (RateUsSettings.IOSAppId) — the
same id used for the rate-prompt store link. Without it the iTunes Lookup falls back to a bundle-id query
and the store deep-link cannot be built, so fill in IOSAppId on the Rate Prompt tab before
shipping iOS update checks.Attribution
Install-source measurement (MMP) integration: where did this player come from, and which campaigns produce payers. Ships with an AppsFlyer backend and a fully offline in-memory stand-in.
| Member | Purpose |
|---|---|
AttributionResolved / LastAttribution | Conversion data once the MMP resolves the install source. |
SendEvent / SendRevenue | In-app and monetary events for campaign optimization. |
SetTrackingConsent(granted) | Gate data collection on the consent module's outcome. |
InstallId | Stable per-install identifier. |
AttributionMode: Disabled, Dummy (in-memory), or
Production — AppsFlyer behind HLK_APPSFLYER with your
dev key. On iOS the SDK start can wait up to AttPromptTimeoutSeconds (default 60) for the
App Tracking Transparency prompt; conversion data can be forwarded onto the analytics bus.
Crash Reporting
Uncaught-exception capture, breadcrumbs, and custom keys behind a swappable backend. The static
facade on CrashReporterService keeps call sites one line long.
| Member | Purpose |
|---|---|
Initialize() / Shutdown() | Hook / unhook the log and exception listeners (bootstrap does this for you). |
LogBreadcrumb / SetCustomKey / SetUserId | Context attached to subsequent reports. |
LogException(ex, context) | Non-fatal exception with optional key/values. |
SetCollectionEnabled(bool) | Runtime kill-switch (tie it to consent). |
TriggerTestCrash() | Verify the pipeline end-to-end on device. |
CrashReporterMode: Disabled (the fresh-import default — nothing is captured),
LogOnly (console echo; select this to see reports without an SDK), or Production
via Firebase Crashlytics behind
HLK_CRASHLYTICS. Settings choose whether uncaught exceptions and
error-level logs are reported, and can stamp a build label on every report.
Native Dialogs
OS-styled alerts, confirmations, action sheets, text prompts, and short toasts through one awaitable API — with a uGUI-rendered fallback so the same calls work in the editor and on unsupported platforms.
| Member | Purpose |
|---|---|
ShowAlertAsync | Single-button notice; returns which button closed it. |
ConfirmAsync | Yes/no question resolving to a bool. |
ShowActionSheetAsync | Option list; resolves to the chosen index. |
ShowPromptAsync / PromptTextAsync | Text-entry dialog. |
ShowToast(message, length) | Short OS toast (Android native; rendered in-engine elsewhere). |
NativeUIMode: InEngine (the built-in renderer) or Native
behind HLK_NATIVE_UI — with
UseInEngineWhenUnsupported (default on) falling back automatically per dialog type.
Runtime Permissions
Cross-platform permission checks and requests for camera, microphone, photo library (read and add),
and when-in-use location, normalized into one PermissionState model.
| Member | Purpose |
|---|---|
Check(permission) | Non-prompting status: Undetermined, Granted, Denied, DeniedPermanently, NotApplicable. |
RequestAsync(permission) | Show the OS prompt when allowed; resolves to the new state. |
EnsureAsync(permission) | Check-then-request convenience returning bool. |
OpenAppSettings() | Deep-link to the app's OS settings page. |
PermissionsMode: Auto, Simulated (always auto-grants — no OS prompt), or
Native behind HLK_NATIVE_PERMISSIONS. Build
post-processors inject the iOS usage-description strings you enter in settings into
Info.plist, and align the Android manifest — no manual plist editing.
Analytics Pipeline
A fan-out event bus: game code emits once, every attached IAnalyticsSink receives the
event. One misbehaving sink can't take the pipeline down — sink exceptions are contained per
dispatch.
Sinks
| Sink | Destination | Requires |
|---|---|---|
DebugLogSink | Unity console (toggleable) | — |
UnityAnalyticsSink | Unity Analytics | HLK_UNITY_ANALYTICS |
FirebaseAnalyticsSink | Firebase Analytics | HLK_FIREBASE |
ForwardingSink | Adapter for hooking arbitrary delegates | — |
AnalyticsMode gates the fan-out: Disabled, ConsoleOnly
(default), or Live (console plus every enabled provider). Other modules publish onto the
same bus when their "bind to analytics" switches are on — quests, offers, IAP funnel steps, ad outcomes,
sharing results, and game-services submissions emit automatically. The battle pass emits its outbound
telemetry the same way, but its inbound XP relies on a bus→IngestEvent forwarder
(shipped at bootstrap) so gameplay events on the bus also drive the pass — see the
Battle Pass section.
Going live (UGS / Firebase)
The stand-in sinks need no setup, but the real providers do:
- Unity Analytics (UGS) — link the project in Project Settings ▸
Services (UGS requires a Cloud project id), install the
com.unity.services.analyticsUPM package, and add HLK_UNITY_ANALYTICS. - Firebase Analytics — import the Firebase SDK, drop
google-services.json(Android) /GoogleService-Info.plist(iOS) underAssets, and add HLK_FIREBASE.
Then switch the mode to Live and enable the matching sink. The Analytics tab checks each
of these prerequisites and reports the effective backend.
Popup Stack
Modal dialogs with serialized queueing, stacking, a shared dimming backdrop, and input blocking.
Popups are prefabs whose root implements IPopupView — derive from PopupBase
and the animation/close plumbing is handled for you.
| Member | Purpose |
|---|---|
Open(prefab, payload, onOpened) | Queue a popup; returns its PopupHandle immediately so you can subscribe to Closed before it shows. |
CloseTop() / Close(handle) / CloseAll() | Dismissal — targeted, topmost, or everything. |
OpenCount / PendingCount / AnyOpen | Stack and queue state (pause gameplay while AnyOpen). |
Ready-made views: MessagePopup (title/body/OK via MessagePopupArgs) and
ConfirmPopup (accept/cancel with an Action<bool> choice callback). A
popup can opt out of backdrop dimming or timescale independence per prefab; the overlay canvas sorts
at order 30000 so popups clear regular UI.
Toast Messages
Transient, non-blocking notices shown strictly one at a time in arrival order. Each toast carries a kind for coloring and its own duration; the queue drains on unscaled time, so toasts keep flowing while the game is paused.
| Member | Purpose |
|---|---|
Show(message, kind, duration) | Enqueue; a duration ≤ 0 falls back to DefaultDuration. |
DefaultDuration | Seconds a toast stays visible when unspecified — 2.2 by default, settable at runtime. |
QueuedCount / IsShowing / ShownCount | Queue introspection. |
BindView(view) / ClearQueue() | Attach the on-screen renderer / drop pending messages. |
Kinds: Info, Success, Warning, Error. With no
view bound (early boot, headless tests) messages are logged instead and the queue still advances — a
later-bound view is never flooded with a backlog. Drop a ToastView prefab in the scene and
ToastBinder connects it to the service.
UI Widgets
Small, dependency-free components that cover the recurring polish work of mobile UI:
| Component | What it does |
|---|---|
SafeAreaFitter | Anchors a RectTransform to Screen.safeArea and re-fits on rotation and resolution changes — notch and cutout safety with zero code. |
NumberTicker | Rolls a TextMeshPro number from its current value to a target with easing, allocation-free (no per-frame string garbage). |
PressFeedback | Squeezes a transform while the pointer is held and springs back on release; runs on unscaled time so buttons stay lively in pause menus. |
RewardClaimButton | Claim flow in a box: designer callback, dismiss animation (Animator trigger or CanvasGroup fade fallback), delayed removal. |
FadeTransitionTrigger | Inspector-wired scene change through IFlowService; logs and no-ops when the flow service is absent instead of throwing. |
Toolset Window
The kit's one configuration surface. Open it via Tools > HLK > Open Toolset
(or Tools > HLK > Guided Setup — same window; the toolset is the setup
flow). It binds directly to the HLKSettings asset and remembers your last tab.
Tab layout
Tabs are arranged in two rows — the core row first, the Pro row after:
| Row | Tabs |
|---|---|
| Core | Overview · Ads · Analytics · Audio · Haptics · Purchases · Mini-Games · Notifications · Rate Prompt · Starter Gifts · General |
| Pro | Ads Pro · App Update · Attribution · Battle Pass · Cloud Save · Consent · Crash Reporter · Deep Links · Energy · Game Services · Purchases Pro · Localization · Native UI · Offers · Permissions · Push · Quests · Remote Config · Sharing · Web View |
What the tabs give you beyond fields
- SDK probes — each vendor-backed tab detects whether the SDK and its scripting define are actually present, and shows what the effective backend will be.
- Pre-flight checks — production-readiness banners (e.g. missing ad unit ids) before you ship with placeholder values.
- Helper actions — one-click steps like adding the Android
VIBRATEpermission, always behind an explicit confirmation dialog. - Asset creation — buttons to generate the preset assets for data-driven modules (also reachable in bulk via Tools > HLK > Generate Starter Config Assets).
Assets/HyperCasualLiveOpsKit/Resources/HLK/HLKSettings.asset — the only path the runtime
loads from.Cheat Console
An on-screen QA console for the actions testers repeat all day. Its compile-time gate keeps releases
clean: with HLK_CHEATS defined you get the live
CheatService plus the console overlay; without it, an inert NoOpCheatService
is registered instead, so any code resolving ICheatService still works — it just refuses
politely.
Opening the console
- Desktop / editor: the toggle key —
BackQuote(`) by default. - Device: press and hold with 3 fingers for 1 second.
Both gestures, keyboard shortcuts, grant amounts (defaults: 1000 coins / 100 gems), and currency ids
are configured in a CheatBindings asset loaded from Resources/HLK/CheatBindings.
Built-in actions
CheatActionId: GrantCoins, GrantGems,
ResetDailyStreak, ClearShopOwnership, WipeAllData — plus
ReloadActiveScene() on the service. When compiled in, a warning is logged every launch as
a reminder to strip the define from release builds.
Scripting Defines
Vendor SDK code never compiles until you opt in. Each integration sits behind an HLK_*
define; install the vendor SDK, make sure the define is set, and the real backend replaces the stand-in.
How the define gets set depends on how the SDK is distributed:
- UPM-package integrations define themselves. These sit behind a
version define in
HLK.Runtime.asmdef, so installing the package automatically adds the define — no manual step: HLK_ADMOB, HLK_UNITY_PURCHASING, HLK_UNITY_ANALYTICS, HLK_FIREBASE, HLK_CRASHLYTICS, HLK_FIREBASE_MESSAGING, HLK_MOBILE_NOTIFICATIONS, HLK_APPSFLYER, HLK_LEVELPLAY, HLK_UNITY_ADS, and HLK_ATT. - Everything else is set manually. SDKs delivered as
.unitypackage/ asset-store imports (and the kit's own compile-time switches) have no version define, so you add the define yourself — via the relevant Toolset tab, or Project Settings → Player → Scripting Define Symbols. This covers HLK_APPLOVIN_MAX, HLK_PLAY_GAMES_SERVICES, HLK_PLAY_GAMES_SAVE, HLK_GAME_CENTER, HLK_NATIVE_SHARE, HLK_ICLOUD, HLK_NATIVE_PERMISSIONS, HLK_NATIVE_WEBVIEW, HLK_NATIVE_UI, HLK_UMP, HLK_PLAY_APP_UPDATE, HLK_FIREBASE_REMOTE_CONFIG, HLK_UGS_REMOTE_CONFIG, HLK_CHEATS, and HLK_LOG_VERBOSE.
| Define | Enables | Module |
|---|---|---|
| HLK_ADMOB | Google Mobile Ads backend | Ads |
| HLK_LEVELPLAY | Unity LevelPlay (ironSource) mediation | Ads |
| HLK_APPLOVIN_MAX | AppLovin MAX mediation | Ads |
| HLK_UNITY_ADS | Unity Ads direct | Ads |
| HLK_UNITY_PURCHASING | Unity IAP store backend | IAP |
| HLK_UNITY_ANALYTICS | Unity Analytics sink | Analytics |
| HLK_FIREBASE | Firebase Analytics sink | Analytics |
| HLK_FIREBASE_MESSAGING | Firebase Cloud Messaging | Push |
| HLK_FIREBASE_REMOTE_CONFIG | Firebase Remote Config backend | Remote Config |
| HLK_UGS_REMOTE_CONFIG | Unity Gaming Services backend | Remote Config |
| HLK_CRASHLYTICS | Firebase Crashlytics backend | Crash Reporting |
| HLK_UMP | Google User Messaging Platform | Consent |
| HLK_ATT | iOS App Tracking Transparency prompt | Consent |
| HLK_APPSFLYER | AppsFlyer SDK | Attribution |
| HLK_PLAY_GAMES_SERVICES | Google Play Games sign-in / leaderboards | Game Services |
| HLK_GAME_CENTER | Apple Game Center | Game Services |
| HLK_PLAY_GAMES_SAVE | Play Games Saved Games | Cloud Save |
| HLK_ICLOUD | iCloud key-value storage | Cloud Save |
| HLK_PLAY_APP_UPDATE | Google Play in-app update flows | App Updates |
| HLK_MOBILE_NOTIFICATIONS | Unity Mobile Notifications backend | Notifications |
| HLK_NATIVE_SHARE | Native share sheet plugin | Sharing |
| HLK_NATIVE_WEBVIEW | Native web-view plugin | Web View |
| HLK_NATIVE_UI | Native dialog backends | Native Dialogs |
| HLK_NATIVE_PERMISSIONS | Native permission requests | Permissions |
| HLK_CHEATS | Compiles the cheat console in | Cheats |
| HLK_LOG_VERBOSE | Verbose diagnostic logging at compile time | Core |
General.VerboseLogging without any define.Troubleshooting
Issues are grouped by module. Diagnostic logging helps with nearly all of them: enable
VerboseLogging on the General tab and watch for [HLK]-tagged lines.
Core & boot
| Symptom | Cause | Fix |
|---|---|---|
Resolve<T>() throws InvalidOperationException |
You resolved before the module's bootstrap ran, or from a script executing before assemblies-loaded hooks. | Resolve in Start() rather than field initializers, or use Services.TryResolve and retry. |
| Settings edits ignored at runtime | The asset isn't at the required Resources path, so a transient default instance is being served. | Keep the asset at Resources/HLK/HLKSettings.asset; the Toolset window creates it correctly. |
| Exceptions from SDK callbacks touching Unity objects | The vendor invoked your handler on a worker thread. | Wrap the body in MonoHost.Post(() => ...). |
Save & cloud
| Symptom | Cause | Fix |
|---|---|---|
| Progress lost when the app is killed quickly | RequestSave() debounces (~1 s) and the flush never ran. |
Call FlushNow() at critical moments and AttachLifecycle() once at boot. |
SyncAsync reports NotSignedIn |
The cloud backend needs platform sign-in that hasn't happened. | Await SignInAsync() first; in the editor use the LocalFallback provider. |
| Cloud adopts the "wrong" copy on conflict | The active ConflictPolicy doesn't match your expectation. |
Pick the policy explicitly on the Cloud Save tab, or pass one to SyncAsync(policy). |
Monetization
| Symptom | Cause | Fix |
|---|---|---|
Rewarded call resolves Suppressed |
A pacing rule blocked it (min interval, session/day cap, quiet levels) or the ad-free entitlement is active. | Read the Reason string; review Ads Pro caps and the placement's overrides. |
| Only mock ads ever appear on device | AdMode is still Dummy, or the mediation SDK/define pair is missing. |
Set Test/Production on the Ads tab, install the SDK, add its define; the tab's probe shows the effective backend. |
PurchaseAsync fails with StoreNotReady |
Initialization didn't finish (or failed) before purchasing. | Await InitializeAsync() and check its bool; verify the catalog has your product id. |
| Restore brings back nothing | Consumables are not restorable by design. | Model permanent items as NonConsumable; verify with a test account that owns them. |
LiveOps
| Symptom | Cause | Fix |
|---|---|---|
| Daily streak resets after one missed day | GraceDays defaults to 0 — strictly consecutive claims. |
Raise GraceDays on the Starter Gifts tab; consider setting a TimeProbeUrl against clock cheats. |
Wheel's TrySelect returns false |
No database assigned, or every slice has weight ≤ 0. | Assign WheelDatabase under Mini-Games and give at least one slice a positive weight. |
| Quests never progress | Event names don't match the definitions, or the window rolled over. | Compare the emitted name with QuestDefinition.EventName; remember Daily/Weekly windows reset UTC. |
| Battle pass XP stays at zero | The event isn't in EventNamesCsv (and AcceptAllEvents is off), or no bus→IngestEvent forwarder is feeding the pass. |
Add the event name to the season config; make sure the bus→IngestEvent forwarder is attached (it ships at bootstrap) so bus events reach the pass, or grant directly with GrantXp. |
| Energy doesn't refill overnight | Reconciliation never ran after the app resumed. | Keep RecalcRegenOnFocus enabled or call Reconcile() when your home screen opens. |
| Review prompt never shows | Throttle gates: 5 sessions first, 14-day spacing, lifetime cap 3, and "stop after rated". | That's working as intended. For QA, use ForceRequestReviewAsync() or ResetGates(). |
Platform services
| Symptom | Cause | Fix |
|---|---|---|
| Notifications never fire in the editor | The editor runs the log-only Dummy backend. | Test on device with Production mode, the Mobile Notifications package, and its define. |
| Push token stays null | Firebase configuration files are missing or the messaging define is absent. | Ship the Firebase platform files (google-services.json on Android, GoogleService-Info.plist on iOS) plus the messaging define; in Dummy mode use SimulateIncoming. |
| Deep link on cold start seems dropped | The link arrived before any subscriber existed. | It's buffered — call DrainPending() right after subscribing to Received. |
| Remote config values never change | The cache window hasn't elapsed, so fetches short-circuit. | Lower CacheSeconds during development or call FetchAsync(force: true). |
| Localized text shows raw keys | No table set assigned, or the key is missing from the active locale. | Assign a TableSet on the Localization tab and add the key; the fallback table covers gaps. |
UI, audio & debug
| Symptom | Cause | Fix |
|---|---|---|
| Toasts log to console instead of showing | No IToastView is bound in the scene. |
Add a ToastView prefab with a ToastBinder; queued behavior is otherwise identical. |
Play(id) produces silence |
The id doesn't exist in the bank, or the bank isn't assigned; category or master may also be muted. | Check the entry name on the Audio tab, the DefaultSoundBank slot, and the mixer toggles. |
| No vibration on Android in Production mode | The VIBRATE permission was never added to the manifest. |
Use the Haptics tab's manifest button; verify device settings allow vibration. |
| Cheat console won't open in a build | HLK_CHEATS wasn't defined for that build target. |
Add the define for QA builds only; on device use the 3-finger 1-second hold. |
| Toon materials render magenta | The material uses a variant from a different render pipeline. | Switch to the family matching your pipeline (BiRP/URP/HDRP); RenderPipelineUtil tells you which is active. |
Currency Icon Setup
CurrencyIcons.Get(CurrencyId) first uses the icon assigned to the active
CurrencyCatalog. If no catalog icon is assigned, it falls back to
Resources.Load<Sprite>("HLK/CurrencyIcons/<CurrencyId>").
For drop-in icons, create Resources/HLK/CurrencyIcons/ and add files such as
Coins.png and Gems.png. Each filename must match its
CurrencyId enum member exactly. Import the image as Sprite (2D and UI), Single mode,
with mipmaps and Read/Write disabled.
Lookups, including misses, are cached. Call CurrencyIcons.InvalidateAll() after
changing icon art or CurrencyIcons.Invalidate(id) to refresh one currency. A fresh
play-mode session starts with an empty cache.
Portable Power-Ups
The PowerUps module keeps definitions, catalogs, inventory state, effect handlers,
the runtime bar, buttons, tutorial popup, setup tools, shared artwork, and the sample Rainbow
Release preset together without embedding game-specific behavior in the definitions.
Add a power-up
- Open Tools > HLK > Power-Ups > Setup & Catalog.
- Choose a permanent ID and display name, then create the definition.
- Assign its center icon and tutorial illustration. The shared slot frame can normally stay unchanged.
- Implement
IPowerUpEffectHandlerin the host game and register it while the gameplay scene is active. - Run Tools > HLK > Power-Ups > Validate.
The default PlayerPrefsPowerUpStateStore supports offline inventory. Install an
account or cloud implementation of IPowerUpStateStore through
PowerUpServices.SetStateStore(...). Catalog ordering, enable flags, duplicate-ID
validation, and an optional display limit control how definitions appear; a zero display limit
shows all enabled definitions in the horizontal scroller.
Release Notes
2.0.0
- Added the independently portable Power-Ups module with scalable catalog, injectable state, generic effect registry, horizontal runtime bar, tutorial template, editor creator, and validation.
- Added daily, weekly, event, and permanent quests; seasonal battle-pass tracks; regenerating energy; and timed offers.
- Added game services, cloud save, push messaging, consent, deep links, app-update prompts, and typed remote config.
- Added catalog-based IAP, mediation-ready ads, and shop items for currencies, ads, unlock gates, and real-money products.
- Added starter gifts, reward wheel, slot and chest minigames, rate prompts, and local notifications.
- Added queued toasts, modal popups, haptics, pooled audio, wallet economy, versioned saves, localization, analytics, sharing, web view, runtime permissions, attribution, crash reporting, and toon shaders.
hce.entitlement.ad_free. Existing HLK_* scripting defines remain unchanged.Licenses & Notices
Robert Penner's Easing Equations
The standard easing constants and curve shapes used by Runtime/Core/Easing.cs,
including the back-overshoot, elastic-period, and bounce-segment parameterizations, are derived
from Robert Penner's easing equations and are used under the BSD License.
Support
Stuck on something this manual doesn't cover? Reach out — real answers from the developer, usually within two business days.
- Email: satisvizion@gmail.com
To get the fastest turnaround, include:
- Unity version, target platform, and render pipeline;
- the module and mode involved (e.g. "Ads, LevelPlay, Production");
- console output with
VerboseLoggingenabled — the[HLK]lines around the failure are usually enough to pinpoint it; - which
HLK_*defines your build sets.
Bug reports that arrive with a minimal reproduction scene get priority. Feature requests are welcome too — a lot of this kit exists because someone asked.
Social Sharing
Native share sheets for text, links, images, screenshots, email, and SMS, wrapped in the static
Sharefacade. Every call resolves to aShareResultthat distinguishes success, cancellation, and failure with a reason.DefaultAsync()TextAsync/ImageAsync/ScreenshotAsyncScreenCaptureHelperbefore opening the sheet.EmailAsync/MessageAsyncCanSendMail()/CanSendMessage()preflight.SharingMode:Disabled,Dummy(logs the request), orNativebehind HLK_NATIVE_SHARE. Share attempts and outcomes can be mirrored onto the analytics bus.