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 elementbuyPriceComponent(element): Reference to BuyPrice componenttimerText(element): Text showing cooldown timerlockedOverlay(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 purchaseUnavailable: Disabled (e.g., insufficient resources)Cooldown: Disabled with countdown timerLocked: 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/countpriceIcon(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 texticonImage(element): Item icon imagecountText(element): Count/amount texticonTimer(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 texticonImage(element): Item iconbuyButton(element): BuyButton componentrewardContainer(element): Container for reward itemsrewardItemPrefab(element): ItemWithAmount prefab for rewardsmainItem(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 namedescriptionText(element): Offer descriptioniconImage(element): Small iconbigImage(element): Large promotional imagetimerText(element): Countdown timerbuyButton(element): BuyButton componentrewardContainer(element): Container for reward itemsrewardItemPrefab(element): ItemWithAmount prefabmainItem(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 itemsdefaultSlotPrefab(element): Default slot prefabtitleText(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 pagesdefaultPagePrefab(element): Default page prefabtabsContainer(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 itemsitemPrefab(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¶
- See Templates for complete working examples
- Check Balancy API for helper methods used by components
- Review Prefabs & Components for system fundamentals