New SDK Preview
This section is a preview of the new SDK. The new SDK is still in development, and we are actively working on improving it. We value your feedback, so if you have any questions or suggestions, please reach out to us. If you are not using the new SDK, please refer to another documentation section.
Daily Bonus¶
Balancy provides a comprehensive Daily Bonus system that allows developers to create engaging daily login rewards for players. The system supports multiple bonus configurations, different reset behaviors, calendar-based rewards, and bonus rewards for completing full cycles. Daily bonuses encourage regular player engagement and can be configured to match various game monetization strategies.
Daily Bonus Structure¶
The Daily Bonus system consists of several key components:
- Daily Bonus Config: Defines the reward sequence, reset behavior, and bonus rewards
- Daily Bonus Info: Tracks individual player progress through the bonus sequence
- Reward System: Regular daily rewards plus optional bonus rewards after completion
- Reset Types: Different behaviors for when the bonus sequence resets
- Time Management: Server-controlled timing for availability and cooldowns
Accessing Daily Bonuses¶
To retrieve the list of available Daily Bonuses, use the following method:
var liveOpsInfo = Balancy.Profiles.System.LiveOpsInfo;
var dailyBonuses = liveOpsInfo.DailyBonusInfos;
foreach (var dailyBonusInfo in dailyBonuses)
{
var bonusConfig = dailyBonusInfo.DailyBonus;
Debug.Log($"Daily Bonus: {bonusConfig?.Name}");
}
const liveOpsInfo = Balancy.Profiles.system.liveOpsInfo;
const dailyBonuses = liveOpsInfo.dailyBonusInfos;
for (let i = 0; i < dailyBonuses.count; i++) {
const dailyBonusInfo = dailyBonuses.get(i);
const bonusConfig = dailyBonusInfo.dailyBonus;
console.log(`Daily Bonus: ${bonusConfig?.name}`);
}
This returns a collection of DailyBonusInfo
objects, each representing a player's progress in a specific daily bonus configuration.
Daily Bonus Types¶
Balancy supports four different daily bonus reset behaviors:
Bonus Type | Description | Use Case |
---|---|---|
Collect All to Reset | The sequence resets only after all rewards are collected | Traditional 7-day login bonus |
Skip to Reset | If the player misses a day, the sequence resets to day 1 | Streak-based rewards requiring consistency |
Calendar Reset | Rewards reset based on calendar cycles (weekly/monthly) | Monthly login calendars |
Skip to Reset (Current Week) | Resets weekly if any reward is skipped | Weekly challenges |
Checking Bonus Type¶
var bonusType = dailyBonusInfo.DailyBonus?.Type;
switch (bonusType)
{
case LiveOpsDailyBonusType.CollectAllToReset:
Debug.Log("Collect all rewards to reset sequence");
break;
case LiveOpsDailyBonusType.SkipToReset:
Debug.Log("Missing a day resets the sequence");
break;
case LiveOpsDailyBonusType.CalendarReset:
Debug.Log("Resets based on calendar schedule");
break;
case LiveOpsDailyBonusType.SkipToResetCurrentWeek:
Debug.Log("Weekly reset if rewards are skipped");
break;
}
const bonusType = dailyBonusInfo.dailyBonus?.type;
switch (bonusType) {
case LiveOpsDailyBonusType.CollectAllToReset:
console.log("Collect all rewards to reset sequence");
break;
case LiveOpsDailyBonusType.SkipToReset:
console.log("Missing a day resets the sequence");
break;
case LiveOpsDailyBonusType.CalendarReset:
console.log("Resets based on calendar schedule");
break;
case LiveOpsDailyBonusType.SkipToResetCurrentWeek:
console.log("Weekly reset if rewards are skipped");
break;
}
Reward Progression¶
Checking Current Progress¶
// Get current progress information
var currentDay = dailyBonusInfo.GetNextRewardNumber();
var rewardsCollected = dailyBonusInfo.DailyRewardCollected;
var lastCollectTime = dailyBonusInfo.DailyRewardCollectTime;
Debug.Log($"Current Day: {currentDay}");
Debug.Log($"Rewards Collected: {rewardsCollected}");
Debug.Log($"Last Collect Time: {lastCollectTime}");
// Get current progress information
const currentDay = dailyBonusInfo.getNextRewardNumber();
const rewardsCollected = dailyBonusInfo.dailyRewardCollected;
const lastCollectTime = dailyBonusInfo.dailyRewardCollectTime;
console.log(`Current Day: ${currentDay}`);
console.log(`Rewards Collected: ${rewardsCollected}`);
console.log(`Last Collect Time: ${lastCollectTime}`);
Checking Next Reward Availability¶
// Check if next reward can be claimed
bool canClaimNext = dailyBonusInfo.CanClaimNextReward();
// Get time until next reward is available (in seconds)
int secondsUntilNext = dailyBonusInfo.GetSecondsTillTheNextReward();
if (canClaimNext)
{
Debug.Log("Next reward is ready to claim!");
}
else
{
Debug.Log($"Next reward available in: {secondsUntilNext} seconds");
}
// Check if next reward can be claimed
const canClaimNext = dailyBonusInfo.canClaimNextReward();
// Get time until next reward is available (in seconds)
const secondsUntilNext = dailyBonusInfo.getSecondsTillTheNextReward();
if (canClaimNext) {
console.log("Next reward is ready to claim!");
} else {
console.log(`Next reward available in: ${secondsUntilNext} seconds`);
}
Working with Rewards¶
Displaying Available Rewards¶
var allRewards = dailyBonusInfo.GetAllRewards();
for (int i = 0; i < allRewards.Length; i++)
{
var reward = allRewards[i];
var dayNumber = i + 1;
var isClaimed = dayNumber <= dailyBonusInfo.DailyRewardCollected;
Debug.Log($"Day {dayNumber} - Claimed: {isClaimed}");
if (reward?.Items != null)
{
foreach (var item in reward.Items)
{
Debug.Log($" - {item?.Item?.Name} x{item?.Count}");
}
}
}
const allRewards = dailyBonusInfo.getAllRewards();
allRewards.forEach((reward, index) => {
const dayNumber = index + 1;
const isClaimed = dayNumber <= dailyBonusInfo.dailyRewardCollected;
console.log(`Day ${dayNumber} - Claimed: ${isClaimed}`);
if (reward?.items) {
reward.items.forEach(item => {
console.log(` - ${item?.item?.name?.value} x${item?.count}`);
});
}
});
Getting Next Reward¶
var nextReward = dailyBonusInfo.GetNextReward();
bool isNextRewardBonus = dailyBonusInfo.IsNextRewardBonus();
if (isNextRewardBonus)
{
Debug.Log("Next reward is the bonus reward!");
var bonusReward = dailyBonusInfo.DailyBonus?.BonusReward;
// Display bonus reward...
}
else if (nextReward != null)
{
Debug.Log("Next regular reward:");
foreach (var item in nextReward.Items)
{
Debug.Log($" - {item?.Item?.Name} x{item?.Count}");
}
}
const nextReward = dailyBonusInfo.getNextReward();
const isNextRewardBonus = dailyBonusInfo.isNextRewardBonus();
if (isNextRewardBonus) {
console.log("Next reward is the bonus reward!");
const bonusReward = dailyBonusInfo.dailyBonus?.bonusReward;
// Display bonus reward...
} else if (nextReward) {
console.log("Next regular reward:");
nextReward.items?.forEach(item => {
console.log(` - ${item?.item?.name?.value} x${item?.count}`);
});
}
Claiming Rewards¶
Basic Reward Claiming¶
var claimedReward = dailyBonusInfo.ClaimNextReward();
if (claimedReward != null)
{
Debug.Log("Successfully claimed reward!");
foreach (var item in claimedReward.Items)
{
Debug.Log($"Received: {item?.Item?.Name} x{item?.Count}");
}
}
else
{
Debug.Log("Failed to claim reward - not available or already claimed");
}
const claimedReward = dailyBonusInfo.claimNextReward();
if (claimedReward) {
console.log("Successfully claimed reward!");
claimedReward.items?.forEach(item => {
console.log(`Received: ${item?.item?.name?.value} x${item?.count}`);
});
} else {
console.log("Failed to claim reward - not available or already claimed");
}
Bonus Rewards¶
When players complete all regular daily rewards, they can claim bonus rewards:
var bonusReward = dailyBonusInfo.DailyBonus?.BonusReward;
if (bonusReward?.Items != null && bonusReward.Items.Length > 0)
{
Debug.Log("Bonus Reward Available:");
foreach (var item in bonusReward.Items)
{
Debug.Log($" - {item?.Item?.Name} x{item?.Count}");
}
// Check if this is the next reward to claim
if (dailyBonusInfo.IsNextRewardBonus() &&
dailyBonusInfo.CanClaimNextReward())
{
Debug.Log("Bonus reward ready to claim!");
}
}
const bonusReward = dailyBonusInfo.dailyBonus?.bonusReward;
if (bonusReward?.items && bonusReward.items.length > 0) {
console.log("Bonus Reward Available:");
bonusReward.items.forEach(item => {
console.log(` - ${item?.item?.name?.value} x${item?.count}`);
});
// Check if this is the next reward to claim
if (dailyBonusInfo.isNextRewardBonus() &&
dailyBonusInfo.canClaimNextReward()) {
console.log("Bonus reward ready to claim!");
}
}
Listening for Updates¶
Since daily bonus availability changes over time, subscribe to updates:
Balancy.Callbacks.OnDailyBonusUpdated += () =>
{
Debug.Log("Daily bonus data updated - refreshing UI");
RefreshDailyBonusDisplay();
};
const dailyBonusUpdatedId = Balancy.Callbacks.onDailyBonusUpdated.subscribe(() => {
console.log("Daily bonus data updated - refreshing UI");
refreshDailyBonusDisplay();
});
// Unsubscribe when component is destroyed
Balancy.Callbacks.onDailyBonusUpdated.unsubscribe(dailyBonusUpdatedId);
Complete Implementation Example¶
Here's a comprehensive example of implementing a daily bonus interface:
public class DailyBonusController : MonoBehaviour
{
[SerializeField] private GameObject dailyBonusPanel;
[SerializeField] private Text bonusNameText;
[SerializeField] private Text bonusTypeText;
[SerializeField] private Text nextRewardTimeText;
[SerializeField] private Button claimButton;
[SerializeField] private Transform rewardGridParent;
[SerializeField] private GameObject rewardCellPrefab;
private List<SmartObjectsDailyBonusInfo> dailyBonuses;
private SmartObjectsDailyBonusInfo selectedBonus;
void Start()
{
Balancy.Callbacks.OnDailyBonusUpdated += RefreshDailyBonus;
claimButton.onClick.AddListener(ClaimReward);
RefreshDailyBonus();
}
void RefreshDailyBonus()
{
if (!Balancy.Main.IsReadyToUse) return;
var liveOpsInfo = Balancy.Profiles.System.LiveOpsInfo;
dailyBonuses = liveOpsInfo.DailyBonusInfos.ToList();
if (dailyBonuses.Count > 0)
{
selectedBonus = dailyBonuses[0]; // Select first bonus
UpdateUI();
}
}
void UpdateUI()
{
if (selectedBonus?.DailyBonus == null) return;
var config = selectedBonus.DailyBonus;
// Update header information
bonusNameText.text = config.Name;
bonusTypeText.text = GetBonusTypeString(config.Type);
// Update timing information
var secondsUntilNext = selectedBonus.GetSecondsTillTheNextReward();
nextRewardTimeText.text = FormatTime(secondsUntilNext);
// Update claim button
var canClaim = selectedBonus.CanClaimNextReward();
claimButton.interactable = canClaim;
claimButton.GetComponentInChildren<Text>().text = canClaim ? "Claim" : "Locked";
// Update reward grid
UpdateRewardGrid();
// Update bonus reward display
UpdateBonusRewardDisplay();
}
void UpdateRewardGrid()
{
// Clear existing reward cells
foreach (Transform child in rewardGridParent)
{
Destroy(child.gameObject);
}
var allRewards = selectedBonus.GetAllRewards();
var currentDay = selectedBonus.GetNextRewardNumber();
for (int i = 0; i < allRewards.Length; i++)
{
var reward = allRewards[i];
var dayNumber = i + 1;
var isClaimed = dayNumber <= selectedBonus.DailyRewardCollected;
var canClaim = dayNumber == currentDay && selectedBonus.CanClaimNextReward();
var rewardCell = Instantiate(rewardCellPrefab, rewardGridParent);
var cellController = rewardCell.GetComponent<RewardCellController>();
cellController.SetReward(reward, dayNumber, isClaimed, canClaim);
cellController.SetClaimAction(() => ClaimReward());
}
}
void UpdateBonusRewardDisplay()
{
var bonusReward = selectedBonus.DailyBonus?.BonusReward;
var isNextRewardBonus = selectedBonus.IsNextRewardBonus();
var canClaimBonus = isNextRewardBonus && selectedBonus.CanClaimNextReward();
if (bonusReward?.Items != null && bonusReward.Items.Length > 0)
{
// Show bonus reward UI
Debug.Log($"Bonus reward available, can claim: {canClaimBonus}");
}
}
void ClaimReward()
{
var claimedReward = selectedBonus.ClaimNextReward();
if (claimedReward != null)
{
Debug.Log("Successfully claimed daily bonus reward!");
// Show reward popup or animation
ShowRewardClaimed(claimedReward);
// Refresh UI
UpdateUI();
}
else
{
Debug.Log("Failed to claim daily bonus reward");
}
}
void ShowRewardClaimed(SmartObjectsReward reward)
{
// Implement reward claim feedback
foreach (var item in reward.Items)
{
Debug.Log($"Claimed: {item?.Item?.Name} x{item?.Count}");
}
}
string GetBonusTypeString(LiveOpsDailyBonusType type)
{
return type switch
{
LiveOpsDailyBonusType.CollectAllToReset => "Collect All to Reset",
LiveOpsDailyBonusType.SkipToReset => "Skip to Reset",
LiveOpsDailyBonusType.CalendarReset => "Calendar Reset",
LiveOpsDailyBonusType.SkipToResetCurrentWeek => "Weekly Reset",
_ => "Unknown"
};
}
string FormatTime(int seconds)
{
if (seconds <= 0) return "Available";
var timeSpan = TimeSpan.FromSeconds(seconds);
return $"{timeSpan.Hours}h {timeSpan.Minutes}m";
}
void OnDestroy()
{
Balancy.Callbacks.OnDailyBonusUpdated -= RefreshDailyBonus;
}
}
export class DailyBonusController {
private dailyBonuses: SmartObjectsDailyBonusInfo[] = [];
private selectedBonus: SmartObjectsDailyBonusInfo | null = null;
private dailyBonusUpdatedId: string;
constructor() {
this.dailyBonusUpdatedId = Balancy.Callbacks.onDailyBonusUpdated.subscribe(() => {
this.refreshDailyBonus();
});
this.refreshDailyBonus();
}
refreshDailyBonus(): void {
if (!Balancy.Main.isReadyToUse) return;
const liveOpsInfo = Balancy.Profiles.system.liveOpsInfo;
this.dailyBonuses = liveOpsInfo.dailyBonusInfos.toArray();
if (this.dailyBonuses.length > 0) {
this.selectedBonus = this.dailyBonuses[0]; // Select first bonus
this.updateUI();
}
}
updateUI(): void {
if (!this.selectedBonus?.dailyBonus) return;
const config = this.selectedBonus.dailyBonus;
// Update header information
console.log(`Bonus Name: ${config.name}`);
console.log(`Bonus Type: ${this.getBonusTypeString(config.type)}`);
// Update timing information
const secondsUntilNext = this.selectedBonus.getSecondsTillTheNextReward();
console.log(`Next reward: ${this.formatTime(secondsUntilNext)}`);
// Update claim status
const canClaim = this.selectedBonus.canClaimNextReward();
console.log(`Can claim: ${canClaim}`);
// Update reward grid
this.updateRewardGrid();
// Update bonus reward display
this.updateBonusRewardDisplay();
}
updateRewardGrid(): void {
const allRewards = this.selectedBonus!.getAllRewards();
const currentDay = this.selectedBonus!.getNextRewardNumber();
allRewards.forEach((reward, index) => {
const dayNumber = index + 1;
const isClaimed = dayNumber <= this.selectedBonus!.dailyRewardCollected;
const canClaim = dayNumber === currentDay && this.selectedBonus!.canClaimNextReward();
console.log(`Day ${dayNumber}: ${isClaimed ? 'Claimed' : canClaim ? 'Available' : 'Locked'}`);
if (reward?.items) {
reward.items.forEach(item => {
console.log(` - ${item?.item?.name?.value} x${item?.count}`);
});
}
});
}
updateBonusRewardDisplay(): void {
const bonusReward = this.selectedBonus!.dailyBonus?.bonusReward;
const isNextRewardBonus = this.selectedBonus!.isNextRewardBonus();
const canClaimBonus = isNextRewardBonus && this.selectedBonus!.canClaimNextReward();
if (bonusReward?.items && bonusReward.items.length > 0) {
console.log(`Bonus reward available, can claim: ${canClaimBonus}`);
bonusReward.items.forEach(item => {
console.log(` Bonus: ${item?.item?.name?.value} x${item?.count}`);
});
}
}
claimReward(): void {
if (!this.selectedBonus) return;
const claimedReward = this.selectedBonus.claimNextReward();
if (claimedReward) {
console.log("Successfully claimed daily bonus reward!");
// Show reward feedback
this.showRewardClaimed(claimedReward);
// Refresh UI
this.updateUI();
} else {
console.log("Failed to claim daily bonus reward");
}
}
showRewardClaimed(reward: SmartObjectsReward): void {
reward.items?.forEach(item => {
console.log(`Claimed: ${item?.item?.name?.value} x${item?.count}`);
});
}
getBonusTypeString(type: LiveOpsDailyBonusType): string {
switch (type) {
case LiveOpsDailyBonusType.CollectAllToReset:
return "Collect All to Reset";
case LiveOpsDailyBonusType.SkipToReset:
return "Skip to Reset";
case LiveOpsDailyBonusType.CalendarReset:
return "Calendar Reset";
case LiveOpsDailyBonusType.SkipToResetCurrentWeek:
return "Weekly Reset";
default:
return "Unknown";
}
}
formatTime(seconds: number): string {
if (seconds <= 0) return "Available";
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours}h ${minutes}m`;
}
destroy(): void {
Balancy.Callbacks.onDailyBonusUpdated.unsubscribe(this.dailyBonusUpdatedId);
}
}
Best Practices¶
Performance Optimization¶
- Cache Daily Bonus Data: Store reference to avoid repeated lookups
- Update Only When Necessary: Use callbacks to refresh UI only when data changes
- Efficient Timer Updates: Update countdown displays at appropriate intervals
- Lazy Loading: Load reward icons and assets only when needed
User Experience¶
- Clear Visual Feedback: Show distinct states for available, locked, and claimed rewards
- Progress Indication: Display how many days completed and remaining
- Bonus Highlight: Make bonus rewards visually distinct and exciting
- Time Communication: Clearly show when next reward will be available
- Celebration Effects: Add satisfying animations for reward claims
Error Handling¶
bool TryClaimReward(SmartObjectsDailyBonusInfo dailyBonusInfo)
{
// Validate bonus exists
if (dailyBonusInfo?.DailyBonus == null)
{
Debug.LogError("Daily bonus configuration not found");
return false;
}
// Check if reward is available
if (!dailyBonusInfo.CanClaimNextReward())
{
var timeLeft = dailyBonusInfo.GetSecondsTillTheNextReward();
Debug.LogWarning($"Reward not available yet. Time remaining: {timeLeft} seconds");
return false;
}
// Attempt to claim
var claimedReward = dailyBonusInfo.ClaimNextReward();
if (claimedReward != null)
{
Debug.Log("Reward claimed successfully");
return true;
}
else
{
Debug.LogError("Failed to claim reward - server validation failed");
return false;
}
}
tryClaimReward(dailyBonusInfo: SmartObjectsDailyBonusInfo): boolean {
// Validate bonus exists
if (!dailyBonusInfo?.dailyBonus) {
console.error("Daily bonus configuration not found");
return false;
}
// Check if reward is available
if (!dailyBonusInfo.canClaimNextReward()) {
const timeLeft = dailyBonusInfo.getSecondsTillTheNextReward();
console.warn(`Reward not available yet. Time remaining: ${timeLeft} seconds`);
return false;
}
// Attempt to claim
const claimedReward = dailyBonusInfo.claimNextReward();
if (claimedReward) {
console.log("Reward claimed successfully");
return true;
} else {
console.error("Failed to claim reward - server validation failed");
return false;
}
}
Using these methods and best practices, you can create a comprehensive Daily Bonus system that drives regular player engagement, provides flexible reward structures, and integrates seamlessly with your game's monetization and analytics systems.