Skip to content

Built-in Scripts

Overview

Balancy provides a library of ready-to-use script components for common UI patterns. These scripts handle shops, offers, items, pricing, and purchase flows - all following best practices and connected to the Balancy backend.

Component Categories:

  • Common: Universal components (buttons, prices)
  • LiveOps: Game-specific components (shops, offers, items)

Component Architecture

Built-in scripts follow a composition pattern where complex components are built from simpler ones:

Shop
 ├─ ShopPage
 │   └─ ShopSlot
 │       ├─ BuyButton
 │       │   └─ BuyPrice
 │       └─ ItemWithAmount (rewards)

GameOffer
 ├─ BuyButton
 │   └─ BuyPrice
 └─ ItemWithAmount (rewards)

GameOfferGroup
 ├─ StoreItem (multiple) 
 │   ├─ BuyButton
 │   │   └─ BuyPrice
 │   └─ ItemWithAmount (rewards)

Common Components

Balancy_UI_Common_BuyButton

Universal buy button that handles all purchase states, cooldowns, and price types.

Serialized Parameters:

  • buttonElement (element): The clickable button element
  • buyPriceComponent (element): Reference to BuyPrice component
  • timerText (element): Text showing cooldown timer
  • lockedOverlay (element): Visual overlay for locked state

Methods:

init(options)

Initialize the buy button.

await buyButton.init({
    price: priceDocument,           // Required: Price document
    storeItem: storeItemDocument,   // Required for Ads price
    onBuy: () => handlePurchase(),  // Callback when clicked
    state: balancy.BuyButtonState.Available,  // Initial state
    cooldownSeconds: 0              // For cooldown state
});

setState(state, stateOptions)

Update button state dynamically.

// Set to cooldown
buyButton.setState(balancy.BuyButtonState.Cooldown, {
    cooldownSeconds: 60
});

// Set to available
buyButton.setState(balancy.BuyButtonState.Available);

// Set to locked
buyButton.setState(balancy.BuyButtonState.Locked);

Button States:

  • Available: Clickable, ready to purchase
  • Unavailable: Disabled (e.g., insufficient resources)
  • Cooldown: Disabled with countdown timer
  • Locked: Disabled with lock overlay

Features:

  • Automatic resource checking for soft currency
  • Visual feedback for insufficient funds
  • Countdown timer with automatic state transition
  • Ads progress tracking
  • Integrates with BuyPrice component

Example:

class MyOffer extends balancy.ElementBehaviour {
    // @serialize {element}
    buyButton = null;

    async start() {
        const script = this.buyButton.getComponent(Balancy_UI_Common_BuyButton);
        await script.init({
            price: this.offer.price,
            storeItem: this.offer.storeItem,
            onBuy: () => this.purchase()
        });
    }

    async purchase() {
        const result = await balancy.buyOffer();
        if (result) {
            this.buyButton.getComponent(Balancy_UI_Common_BuyButton)
                .setState(balancy.BuyButtonState.Cooldown, {
                    cooldownSeconds: 3600
                });
        }
    }
}

Balancy_UI_Common_BuyPrice

Displays price information with icon and text. Handles all price types: Hard (IAP), Soft (virtual currency), and Ads.

Serialized Parameters:

  • priceText (element): Text element for price/count
  • priceIcon (element): Icon element for currency/item

Methods:

init(price, storeItem)

Initialize the price display.

await buyPrice.init(priceDocument, storeItemDocument);

Price Type Handling:

Hard Currency (IAP):

  • Shows localized price from store (e.g., "$4.99")
  • Hides icon

Soft Currency:

  • Shows amount and currency icon
  • Example: "100" with coin icon

Ads:

  • Shows progress if multiple ads required
  • Example: "2/3" or "Watch Ad"

Free:

  • Shows localized "FREE" text

Example:

class PriceDisplay extends balancy.ElementBehaviour {
    // @serialize {element}
    priceComponent = null;

    async start() {
        const script = this.priceComponent.getComponent(Balancy_UI_Common_BuyPrice);
        await script.init(
            this.storeItem.price,
            this.storeItem
        );
    }
}

LiveOps Components

Balancy_UI_LiveOps_ItemWithAmount

Displays an item with its quantity. Used for rewards and prices.

Serialized Parameters:

  • nameText (element): Item name text
  • iconImage (element): Item icon image
  • countText (element): Count/amount text
  • iconTimer (element): Timer icon (for decay effects)
  • timeFormat (number): Time display format (0-2)
  • currencyFormat (number): Currency number format (0-5)
  • regularItemFormat (number): Regular item number format (0-5)
  • timePrecision (number): Time precision (0-5)

Methods:

init(itemWithAmount)

Initialize with an ItemWithAmount document.

itemScript.init(itemWithAmountDocument);

Formatting Options:

The component automatically formats counts based on item type: - Currency: Plain numbers (e.g., "100") - Regular items: With prefix (e.g., "x50") - Time-based items: Localized time (e.g., "5h 32m")

Example:

class RewardsList extends balancy.ElementBehaviour {
    // @serialize {element}
    itemPrefab = null;
    // @serialize {element}
    container = null;

    displayRewards(rewards) {
        this.container.element.innerHTML = '';

        for (const item of rewards.items) {
            const itemObject = this.itemPrefab.instantiate();
            const script = itemObject.getComponent(Balancy_UI_LiveOps_ItemWithAmount);
            script.init(item);
            this.container.element.appendChild(itemObject.element);
        }
    }
}

Balancy_UI_LiveOps_StoreItem

Displays a purchasable store item with name, icon, price, rewards, and buy button.

Serialized Parameters:

  • titleText (element): Item name text
  • iconImage (element): Item icon
  • buyButton (element): BuyButton component
  • rewardContainer (element): Container for reward items
  • rewardItemPrefab (element): ItemWithAmount prefab for rewards
  • mainItem (element): Main reward display (optional)

Methods:

init(storeItem, options)

Initialize the store item.

await storeItem.init(storeItemDocument, {
    state: balancy.BuyButtonState.Available,
    cooldownSeconds: 0,
    onBuy: (item) => handlePurchase(item)
});

Features:

  • Displays item name and icon
  • Shows all rewards (with optional main item highlight)
  • Integrates with BuyButton for purchase flow
  • Supports all button states and cooldowns

Example:

class ShopSlot extends balancy.ElementBehaviour {
    // @serialize {element}
    storeItemComponent = null;

    async init(slotData) {
        const script = this.storeItemComponent.getComponent(Balancy_UI_LiveOps_StoreItem);
        await script.init(slotData.storeItem, {
            onBuy: (item) => this.purchase(item)
        });
    }

    async purchase(item) {
        const result = await balancy.buyShopSlot(item.unnyId);
        console.log('Purchase result:', result);
    }
}

Balancy_UI_LiveOps_GameOffer

Displays a single game offer with timer, description, and purchase functionality.

Serialized Parameters:

  • titleText (element): Offer name
  • descriptionText (element): Offer description
  • iconImage (element): Small icon
  • bigImage (element): Large promotional image
  • timerText (element): Countdown timer
  • buyButton (element): BuyButton component
  • rewardContainer (element): Container for reward items
  • rewardItemPrefab (element): ItemWithAmount prefab
  • mainItem (element): Main reward display (optional)

Methods:

init(offerInfo)

Initialize with OfferInfo runtime wrapper.

offerScript.init(offerInfoObject);

Features:

  • Displays offer details (name, description, images)
  • Live countdown timer (updates every second)
  • Auto-closes when timer expires
  • Shows all rewards
  • Integrated purchase flow
  • Calls balancy.buyOffer() on purchase

Example:

// In your main view script
async function main() {
    const offers = await balancy.getActiveOffers();
    const offerInfo = offers[0];

    const viewObject = await balancy.instantiatePrefabById('offer-view');
    const script = viewObject.getComponent(Balancy_UI_LiveOps_GameOffer);
    script.init(offerInfo);

    document.body.appendChild(viewObject.element);
}

Balancy_UI_LiveOps_GameOfferGroup

Displays a group offer with multiple purchase options.

Similar to GameOffer but handles group offers with multiple slots.

Methods:

init(offerGroupInfo)

Initialize with OfferGroupInfo runtime wrapper.

groupScript.init(offerGroupInfoObject);

Balancy_UI_LiveOps_ShopSlot

Displays a shop slot with purchase functionality.

Serialized Parameters:

  • storeItemComponent (element): StoreItem component
  • Additional slot-specific parameters

Methods:

init(shopSlot, options)

Initialize the shop slot.

await slot.init(shopSlotData, {
    onBuy: (slot) => handleSlotPurchase(slot)
});

Balancy_UI_LiveOps_ShopPage

Displays a single page of a shop with multiple slots.

Serialized Parameters:

  • slotsContainer (element): Container for slot items
  • defaultSlotPrefab (element): Default slot prefab
  • titleText (element): Page title

Methods:

init(shopPage)

Initialize with ShopPage runtime object.

pageScript.init(shopPageObject);

Features:

  • Creates slot instances based on shop configuration
  • Uses custom slot prefabs if specified in data
  • Falls back to default slot prefab
  • Handles slot purchase callbacks

Balancy_UI_LiveOps_Shop

Displays the entire shop with multiple pages. Supports two modes: Scroll and Tabs.

Serialized Parameters:

  • pagesContainer (element): Container for pages
  • defaultPagePrefab (element): Default page prefab
  • tabsContainer (element): Container for tab buttons (optional)
  • tabPrefab (element): Tab button prefab (optional)
  • useScrollMode (boolean): Scroll mode vs tabs mode

Methods:

init(shop)

Initialize with Shop runtime object.

shopScript.init(shopRuntimeObject);

switchToPage(index)

Programmatically switch to a page.

shopScript.switchToPage(1);  // Switch to page 2

refresh()

Refresh all pages (e.g., after purchase).

shopScript.refresh();

Display Modes:

Scroll Mode (useScrollMode = true):

  • All pages rendered in scrollable container
  • Tabs provide quick navigation via smooth scrolling
  • All pages always visible

Tabs Mode (useScrollMode = false):

  • Only one page visible at a time
  • Tabs switch between pages
  • Better performance for many pages

Example:

// In your shop view script
async function main() {
    const shopData = window.balancyViewOwner;

    const shopObject = await balancy.instantiatePrefabById('shop-view');
    const script = shopObject.getComponent(Balancy_UI_LiveOps_Shop);
    script.init(shopData);

    document.getElementById('shop-container').appendChild(shopObject.element);
}

Balancy_UI_LiveOps_Reward

Displays a reward collection (multiple ItemWithAmount objects).

Serialized Parameters:

  • itemsContainer (element): Container for reward items
  • itemPrefab (element): ItemWithAmount prefab to instantiate

Methods:

init(reward)

Initialize with Reward document containing multiple items.

rewardScript.init(rewardDocument);

Features:

  • Instantiates ItemWithAmount prefab for each item in reward
  • Clears container before adding items
  • Handles rewards with multiple items automatically

Example:

class MyOffer extends balancy.ElementBehaviour {
    // @serialize {element}
    rewardComponent = null;

    init(offer) {
        const rewardScript = this.rewardComponent.getComponent(Balancy_UI_LiveOps_Reward);
        rewardScript.init(offer.storeItem.reward);
        // Automatically displays all items in the reward
    }
}

Component Relationships

Shop → ShopPage → ShopSlot Hierarchy

// Shop creates pages
class Shop {
    async _createPages() {
        for (const shopPage of this._shop.pages) {
            // Use page's custom view or default page prefab
            const viewId = shopPage.page?.unnyView?.id;
            const pageObject = viewId
                ? await balancy.instantiatePrefabById(viewId)
                : this.defaultPagePrefab.instantiate();

            const pageScript = pageObject.getComponent(Balancy_UI_LiveOps_ShopPage);
            pageScript.init(shopPage);
        }
    }
}

// ShopPage creates slots
class ShopPage {
    async _createSlots() {
        for (const shopSlot of this._shopPage.slots) {
            // Use slot's custom view or default slot prefab
            const viewId = shopSlot.unnyView?.id;
            const slotObject = viewId
                ? await balancy.instantiatePrefabById(viewId)
                : this.defaultSlotPrefab.instantiate();

            const slotScript = slotObject.getComponent(Balancy_UI_LiveOps_ShopSlot);
            slotScript.init(shopSlot);
        }
    }
}

// ShopSlot is self-contained (similar to StoreItem but for shops)
class ShopSlot {
    async init(shopSlot) {
        const storeItem = shopSlot.slot.storeItem;

        // Apply name, icon, rewards
        this._applyName(storeItem);
        this._applyIcon(storeItem);

        // Initialize buy button
        const buyButtonScript = this.buyButton.getComponent(Balancy_UI_Common_BuyButton);
        buyButtonScript.init({
            price: storeItem.price,
            storeItem: storeItem,
            onBuy: () => this._handleBuy()
        });
    }
}

BuyButton → BuyPrice Composition

class StoreItem {
    _initBuyButton() {
        const buyButton = this.buyButton.getComponent(Balancy_UI_Common_BuyButton);
        buyButton.init({
            price: this._storeItem.price,
            storeItem: this._storeItem,
            onBuy: () => this._handleBuy()
        });
    }
}

// BuyButton internally uses BuyPrice
class BuyButton {
    awake() {
        this._buyPriceScript = this.buyPriceComponent
            .getComponent(Balancy_UI_Common_BuyPrice);
    }

    async init(options) {
        await this._buyPriceScript.init(options.price, options.storeItem);
    }
}

Helper Enums & Constants

balancy.BuyButtonState

balancy.BuyButtonState.Available    // 0 - Ready to purchase
balancy.BuyButtonState.Unavailable  // 1 - Disabled (e.g., not enough resources)
balancy.BuyButtonState.Cooldown     // 2 - On cooldown with timer
balancy.BuyButtonState.Locked       // 3 - Locked with overlay

balancy.PriceType

balancy.PriceType.Hard   // 1 - Real money (IAP)
balancy.PriceType.Soft   // 2 - Virtual currency
balancy.PriceType.Ads    // 3 - Rewarded ads

Best Practices

1. Initialize in Correct Lifecycle Method

class MyView extends balancy.ElementBehaviour {
    // @serialize {element}
    buyButton = null;

    // ✓ Good: Initialize in start() or awake()
    async awake() {
        const script = this.buyButton.getComponent(Balancy_UI_Common_BuyButton);
        await script.init({ /* ... */ });
    }

    // ✗ Avoid: Initializing in update()
    update(deltaTime) {
        // This would re-initialize every frame!
    }
}

2. Use Composition

// ✓ Good: Compose from smaller components
<div id="store-item">
    <div data-script-id="Balancy_UI_LiveOps_StoreItem"></div>
    <!-- StoreItem uses BuyButton internally -->
</div>

// ✗ Avoid: Duplicating button logic
<div id="store-item">
    <div data-script-id="CustomStoreItemWithOwnButtonLogic"></div>
</div>

3. Handle Async Initialization

class MyShop extends balancy.ElementBehaviour {
    async awake() {
        // Delay ready signal
        balancy.delayIsReady();

        // Initialize async components
        await this.initShop();

        // Signal ready
        balancy.sendIsReady();
    }
}

4. Clean Up Resources

class MyComponent extends balancy.ElementBehaviour {
    onDestroy() {
        // Clean up instantiated objects
        if (this._itemObjects) {
            this._itemObjects.forEach(obj => {
                balancy.ElementsManager.destroy(obj.element);
            });
        }
    }
}

Common Patterns

Dynamic List Creation

class RewardsList extends balancy.ElementBehaviour {
    // @serialize {element}
    container = null;
    // @serialize {element}
    itemPrefab = null;

    async displayItems(items) {
        // Clear previous items
        this.container.element.innerHTML = '';

        // Create item instances
        for (const item of items) {
            const itemObject = this.itemPrefab.instantiate();
            const script = itemObject.getComponent(Balancy_UI_LiveOps_ItemWithAmount);
            script.init(item);
            this.container.element.appendChild(itemObject.element);
        }
    }
}

Purchase Flow with State Management

class PurchaseHandler extends balancy.ElementBehaviour {
    // @serialize {element}
    buyButton = null;

    async purchase() {
        const button = this.buyButton.getComponent(Balancy_UI_Common_BuyButton);

        try {
            // Attempt purchase
            const result = await balancy.buyOffer();

            if (result.success) {
                // Set cooldown state
                button.setState(balancy.BuyButtonState.Cooldown, {
                    cooldownSeconds: result.cooldownTime
                });
            } else {
                // Show error, keep available
                this.showError(result.error);
            }
        } catch (error) {
            console.error('Purchase failed:', error);
        }
    }
}

Conditional Component Display

class OfferView extends balancy.ElementBehaviour {
    // @serialize {element}
    timerText = null;
    // @serialize {element}
    limitedBadge = null;

    init(offerInfo) {
        // Show/hide elements based on offer type
        if (offerInfo.hasTimeLimit) {
            this.timerText.element.style.display = '';
            this.limitedBadge.element.style.display = '';
        } else {
            this.timerText.element.style.display = 'none';
            this.limitedBadge.element.style.display = 'none';
        }
    }
}

Extending Built-in Components

You can extend built-in components to customize behavior:

class CustomBuyButton extends Balancy_UI_Common_BuyButton {
    async _handleClick() {
        // Add custom analytics
        analytics.track('button_clicked', {
            item: this._storeItem.unnyId
        });

        // Call parent implementation
        await super._handleClick();
    }

    _applyState() {
        super._applyState();

        // Add custom visual effects
        if (this._state === balancy.BuyButtonState.Available) {
            this.addPulseAnimation();
        }
    }
}

// Register your custom version
balancy.ElementsManager.registerScript('CustomBuyButton', CustomBuyButton);

Next Steps