Working with Views¶
Overview¶
Views are Balancy assets that combine HTML, CSS, and JavaScript to create interactive user interfaces. This guide covers how to open views from your game code, pass data to them, and establish two-way communication between your game and the WebView.
Opening Views¶
Basic Usage¶
GameOffers and GameEvents have a UnnyView parameter. You can also create custom parameters of type Object with subtype View.
To open a view from your game code:
UnnyView.OpenView(onShownCallback, ownerObject);
unnyView.openView(onShownCallback, ownerObject);
Parameters¶
onShownCallback (optional): Callback function that will be called when the view is shown.
- In most cases it's called instantly
- May take time if elements need to be loaded (images, fonts, etc.)
- Use for post-initialization logic
ownerObject (optional): Object that will be assigned to window.balancyViewOwner in the View's JavaScript context.
- Pass additional data to the view
- Usually used to pass GameEvent or GameOffer parameters
- Accessible in JavaScript as
window.balancyViewOwner
Example¶
// Open offer view
var offer = Balancy.LiveOps.Offers.GetActiveOffer("special_pack");
offer.UnnyView.OpenView(
onShown: () => {
Debug.Log("Offer view is now visible");
},
ownerObject: offer
);
// Open event view with custom data
var eventData = new Dictionary<string, object> {
{ "eventId", "summer_event" },
{ "progress", 75 },
{ "rewards", rewardsList }
};
myEvent.UnnyView.OpenView(null, eventData);
// Open offer view
const offer = Balancy.LiveOps.Offers.getActiveOffer("special_pack");
offer.unnyView.openView(
() => {
console.log("Offer view is now visible");
},
offer
);
// Open event view with custom data
const eventData = {
eventId: "summer_event",
progress: 75,
rewards: rewardsList
};
myEvent.unnyView.openView(null, eventData);
Closing Views¶
From JavaScript¶
Views should include a close button that calls balancy.closeView():
// Close button handler
document.getElementById('close-btn').onclick = () => {
balancy.closeView('User clicked close');
};
// Auto-close when timer expires
if (timeLeft <= 0) {
balancy.closeView('Timer expired');
}
Automatic Close Button¶
Balancy automatically adds an invisible close button in the top-right corner of every view.
Purpose:
- Provides a fallback if JavaScript crashes
- Ensures users can always close the view
- Cannot be disabled
Best Practice:
- Always design your own visible close button
- Position it clearly for users
- Use consistent close button design across views
Passing Data to Views¶
Using window.balancyViewOwner¶
The ownerObject parameter becomes available as window.balancyViewOwner in JavaScript:
Game Code (C#):
var offerData = new {
offerId = "summer_pack",
discount = 50,
originalPrice = 9.99,
salePrice = 4.99
};
myView.OpenView(null, offerData);
View JavaScript:
window.addEventListener('balancy-ready', () => {
const owner = window.balancyViewOwner;
console.log('Offer ID:', owner.offerId);
console.log('Discount:', owner.discount);
// Update UI
document.getElementById('discount').textContent = `${owner.discount}% OFF`;
document.getElementById('price').textContent = `$${owner.salePrice}`;
});
Typical Owner Data Structure¶
For GameOffers:
window.balancyViewOwner = {
instanceId: "f09ab140-3752-4593-98e3-48a31046",
gameOffer: null, // Will be resolved automatically
unnyIdGameOffer: "1188", // Used for document resolution
productId: "coin_pack_small",
status: 1,
// ... other offer fields
};
For GameEvents:
window.balancyViewOwner = {
unnyIdGameEvent: "663",
eventName: "Summer Festival",
startTime: 1751328000,
endTime: 1751414400,
// ... other event fields
};
Custom Messages¶
Balancy provides two-way communication between your game code and the WebView. This allows you to send messages back and forth, with request-response patterns and broadcasting.
Message Flow Overview¶
Game (C#/TypeScript) ←→ WebView (JavaScript)
↓ ↓
SetOnMessageReceivedCallback sendCustomMessage()
SendResponseToView subscribeToCustomMessages()
sendCustomMessageToView (event listeners)
JavaScript to SDK: Request-Response Pattern¶
Send custom messages from JavaScript and receive responses from your game code.
JavaScript Side¶
Send a message with balancy.sendCustomMessage():
balancy.sendCustomMessage('my_button', {
param: 'myParam',
value: 42
}).then(response => {
console.info("Custom message response:", response);
}).catch(error => {
console.error("Custom message error:", error);
});
Default Response:
Unless you override it, you'll get:
{
"status": "ok"
}
SDK Side: Intercepting and Responding¶
Set up a message interceptor in your game code to catch messages and send custom responses:
[System.Serializable]
class MessageExample
{
public string type;
public string sender;
public string id;
public int action;
}
Balancy.Actions.View.SetOnMessageReceivedCallback((msg) =>
{
//msg = {"type":"request","sender":"my_button","id":"58","action":1000,"params":{"param":"myParam","value":42}}
var messageInfo = JsonUtility.FromJson<MessageExample>(msg);
if (messageInfo.action == 1000)
{
// Send custom response back to JavaScript
Balancy.Actions.View.SendResponseToView(
messageInfo.id,
"{\"response\":\"Hello from Unity!\"}"
);
return false; // Don't let Balancy process this message
}
return true; // Let Balancy handle other messages
});
interface MessageExample {
type: string;
sender: string;
id: string;
action: number;
}
Balancy.Actions.View.setOnMessageReceivedCallback((msg: string): boolean => {
//msg = {"type":"request","sender":"my_button","id":"58","action":1000,"params":{"param":"myParam","value":42}}
const messageInfo: MessageExample = JSON.parse(msg);
if (messageInfo.action === 1000) {
// Send custom response back to JavaScript
Balancy.Actions.View.sendResponseToView(
messageInfo.id,
JSON.stringify({response: "Hello from TypeScript!"})
);
return false; // Don't let Balancy process this message
}
return true; // Let Balancy handle other messages
});
Important:
- Return
falseafter sending your custom response to prevent Balancy from sending the default response - Return
trueto let Balancy continue processing the message
Detecting View Closing¶
A common use case is detecting when a view is being closed. The close event is sent with action: 200 (RequestAction.CloseWindow).
Example Message Format¶
When the view is closing, you'll receive one of these:
{"action":200, "params":{}}
or
{"type":"request","sender":"ilmsd-2","id":"42","action":200,"params":{}}
Both indicate that the view is about to close.
Tracking View Closing¶
[System.Serializable]
class MessageExample
{
public int action;
}
Balancy.Actions.View.SetOnMessageReceivedCallback((msg) =>
{
var messageInfo = JsonUtility.FromJson<MessageExample>(msg);
if (messageInfo.action == 200)
{
Debug.Log("View is closing!");
// Perform any cleanup or tracking here
// Log analytics event
// Save player preferences
// Resume game music
}
return true; // Continue processing - let Balancy close the view
});
interface MessageExample {
action: number;
}
Balancy.Actions.View.setOnMessageReceivedCallback((msg: string): boolean => {
const messageInfo: MessageExample = JSON.parse(msg);
if (messageInfo.action === 200) {
console.log("View is closing!");
// Perform any cleanup or tracking here
// Log analytics event
// Save player preferences
// Resume game music
}
return true; // Continue processing - let Balancy close the view
});
SDK to JavaScript: Broadcasting Messages¶
Send one-way messages from your game to JavaScript that will be broadcast to all subscribers.
SDK Side: Sending Messages¶
// Send a custom message to the WebView
Balancy.Actions.View.SendCustomMessageToView(
"{\"action\":\"greeting\", \"message\":\"Hello from Unity!\"}"
);
// Send game state updates
Balancy.Actions.View.SendCustomMessageToView(
"{\"action\":\"score-update\", \"score\":" + currentScore + "}"
);
// Trigger animations
Balancy.Actions.View.SendCustomMessageToView(
"{\"action\":\"play-animation\", \"name\":\"victory\"}"
);
// Send a custom message to the WebView
Balancy.Actions.View.sendCustomMessageToView(
JSON.stringify({action: "greeting", message: "Hello from TypeScript!"})
);
// Send game state updates
Balancy.Actions.View.sendCustomMessageToView(
JSON.stringify({action: "score-update", score: currentScore})
);
// Trigger animations
Balancy.Actions.View.sendCustomMessageToView(
JSON.stringify({action: "play-animation", name: "victory"})
);
JavaScript Side: Receiving Messages¶
Subscribe to these messages in your view:
// Method 1: Subscribe with callback (recommended)
const unsubscribe = balancy.subscribeToCustomMessages((data) => {
console.info('Received custom message:', data);
if (data.action === 'greeting') {
console.log('Message:', data.message);
showNotification(data.message);
}
if (data.action === 'score-update') {
updateScoreDisplay(data.score);
}
if (data.action === 'play-animation') {
playAnimation(data.name);
}
});
// To unsubscribe later:
unsubscribe();
// Method 2: Listen to DOM event
window.addEventListener('balancy-custom-message', (event) => {
console.log('Received custom message:', event.detail);
});
Multiple Subscribers¶
Messages are broadcast to all subscribers:
// Analytics subscriber
balancy.subscribeToCustomMessages((data) => {
if (data.action === 'track-event') {
analytics.track(data.eventName, data.properties);
}
});
// UI subscriber
balancy.subscribeToCustomMessages((data) => {
if (data.action === 'update-ui') {
refreshUI();
}
});
// Both subscribers receive all messages
Key Features:
- Messages are broadcast to all subscribers
- Multiple subscribers can listen simultaneously
- Errors in one subscriber don't affect others
- Both callback-based and event-based subscription methods available
Complete Custom Message Examples¶
Example 1: Level Selection¶
JavaScript (sending request):
document.getElementById('level-3').onclick = async () => {
const response = await balancy.sendCustomMessage('level_select', {
levelId: 3,
difficulty: 'hard'
});
if (response.canPlay) {
balancy.closeView('Starting level');
} else {
alert(`Level locked! Unlock at player level ${response.requiredLevel}`);
}
};
C# (responding):
Balancy.Actions.View.SetOnMessageReceivedCallback((msg) =>
{
var messageInfo = JsonUtility.FromJson<CustomMessage>(msg);
if (messageInfo.action == 1000 && messageInfo.sender == "level_select")
{
var levelId = messageInfo.params["levelId"];
var canPlay = PlayerData.CanPlayLevel(levelId);
var response = new {
canPlay = canPlay,
requiredLevel = canPlay ? 0 : 10
};
Balancy.Actions.View.SendResponseToView(
messageInfo.id,
JsonUtility.ToJson(response)
);
return false;
}
return true;
});
Example 2: Real-time Score Updates¶
C# (broadcasting during gameplay):
void OnScoreChanged(int newScore)
{
if (isShopViewOpen)
{
var message = new {
action = "score-update",
score = newScore,
currency = PlayerData.GetCoins()
};
Balancy.Actions.View.SendCustomMessageToView(
JsonUtility.ToJson(message)
);
}
}
JavaScript (receiving and updating UI):
balancy.subscribeToCustomMessages((data) => {
if (data.action === 'score-update') {
// Animate score change
animateCounterChange('score', data.score);
animateCounterChange('coins', data.currency);
// Check if player can now afford items
updateButtonStates();
}
});
Best Practices¶
1. Always Handle Message Errors¶
// ✓ Good: Handle both success and failure
balancy.sendCustomMessage('action', data)
.then(response => handleSuccess(response))
.catch(error => handleError(error));
// ✗ Avoid: No error handling
const response = await balancy.sendCustomMessage('action', data);
2. Use Action Codes Consistently¶
Define constants for action codes:
public static class CustomActions
{
public const int LevelSelect = 1000;
public const int EquipItem = 1001;
public const int CraftItem = 1002;
}
if (messageInfo.action == CustomActions.LevelSelect) { ... }
const CustomActions = {
LevelSelect: 1000,
EquipItem: 1001,
CraftItem: 1002
};
balancy.sendCustomMessage('level_select', {
action: CustomActions.LevelSelect,
levelId: 3
});
3. Return Appropriate Boolean Values¶
// ✓ Good: Return false when sending custom response
Balancy.Actions.View.SendResponseToView(messageInfo.id, customResponse);
return false; // Prevent default response
// ✓ Good: Return true for other messages
if (messageInfo.action == 200) {
Debug.Log("View closing");
return true; // Let Balancy handle it
}
4. Clean Up Subscriptions¶
// ✓ Good: Unsubscribe when view closes
const unsubscribe = balancy.subscribeToCustomMessages(handler);
window.addEventListener('beforeunload', () => {
unsubscribe();
});
// Or in component lifecycle
class MyComponent extends balancy.ElementBehaviour {
awake() {
this.unsubscribe = balancy.subscribeToCustomMessages(
(data) => this.handleMessage(data)
);
}
onDestroy() {
this.unsubscribe();
}
}
5. Document Your Custom Actions¶
Maintain a shared document listing all custom actions:
Action 1000: Level Selection
JS → SDK: { levelId: number, difficulty: string }
SDK → JS: { canPlay: boolean, requiredLevel: number }
Action 1001: Equip Item
JS → SDK: { itemId: string, slot: string }
SDK → JS: { success: boolean, error?: string }
Troubleshooting¶
Messages Not Being Received¶
Problem: SDK callback never called.
Solutions:
- Verify callback was set before view opened
- Check action code matches (e.g., 1000 vs 1001)
- Look for JSON parsing errors in logs
- Ensure message format is correct
Response Not Reaching JavaScript¶
Problem: Promise never resolves in JavaScript.
Solutions:
- Verify
SendResponseToView()is called with correct message ID - Check you're returning
falseafter sending custom response - Ensure response is valid JSON
- Look for errors in browser console
Broadcast Messages Not Received¶
Problem: subscribeToCustomMessages() callback not firing.
Solutions:
- Verify subscription happened before message was sent
- Check
sendCustomMessageToView()is being called - Ensure JSON format is correct
- Look for JavaScript errors that might stop execution
View Not Closing¶
Problem: View stays open after close message.
Solutions:
- Verify you're returning
truefor action 200 (don't block it) - Check
balancy.closeView()is being called in JavaScript - Look for JavaScript errors preventing close
- Remember automatic close button is always available
Next Steps¶
- Learn Events System for lifecycle events and notifications
- Check Balancy API for JavaScript methods
- See Prefabs & Components for reusable UI elements
- Explore Templates for ready-to-use view examples