Hi. Can someone explain to me how to separate binding as unique identifier? So I have 3 buildings of the same type and they have the same template with this binding shown in this image below. This is work fine without any issue but it does update this binding for all of three building but what I’m trying to do is give the player possibility to start building whenever they want separately so each building have the same time with this binding how to separate it for each instance?
var binding = building.GetBinding("text") as LocalizedString;
var buildCount = binding["hours"] as IntVariable;
buildCount.Value = build.hours;
var buildCount2 = binding["minutes"] as IntVariable;
buildCount2.Value = build.minutes;
var buildCount3 = binding["seconds"] as IntVariable;
buildCount3.Value = build.seconds;
Help needed @martinpa_unity @karl_jones
Bump!! Really stuck on it. I tried to search about it… but there’s nothing saying on how to separate binding to a unique instance for each buildings
For example in uGUI each instance has it’s own text but in UI TK using bindings like this update all of them no matter what. Why? Is it because bindings only work for 1 variable so It doesn’t create any other instances?
Really stuck on this. Any help would be appreaciated since I can’t find anything like this in documentations. @karl_jones @martinpa_unity
I’m not sure I understand the question. You want 3 instances of the text but they all share the same variables? You could try using global variables instead or set them up via code at runtime.
https://docs.unity3d.com/Packages/com.unity.localization@1.5/manual/Smart/Persistent-Variables-Source.html
I have a single template that stands for showing information about that building so in template I have a label called “SetupTimeText” that I add a binding to localize this string as a timer like 1h 30m 25s so I used variables to setup properly the value which is working great but only for one instance. Even if I try to isolate IntVariable for each instances Timer counter still tick for all of them. (Keep in mind this is the same exact type of building) that sharing the same exact info.
Using Localization binding on the Label doesn’t allow me to setup multiple instances.
After reading it… it doesn’t seems to cover that part anywhere.
P.S. And yes I have a script that manage instances of the same type if I don’t use Localization at all It works as expected but what I’m trying to do is keep translated letters with timer going on.
Anyway I tried using group variables but it still tick Timer for all of them it’s the same as binding previously
Slot is stand for each instance even of the same type
TimerCounter = rootChestInfo.Q<Label>("SetupTimeText");
var source = LocalizationSettings.StringDatabase.SmartFormatter.GetSourceExtension<PersistentVariablesSource>();
slot.HoursTimer = source["global"]["hours"] as IntVariable;
slot.MinutesTimer = source["global"]["minutes"] as IntVariable;
slot.SecondsTimer = source["global"]["seconds"] as IntVariable;
slot.HoursTimer.Value = slot.Hours;
slot.MinutesTimer.Value = slot.Minutes;
slot.SecondsTimer.Value = slot.Seconds;
var localizedString = new LocalizedString("General UI Text", "key_timer");
TimerCounter.text = localizedString.GetLocalizedString();
A class to handle slots separately even of the same type
[System.Serializable]
public class ChestSlot
{
[JsonIgnore]
[field:SerializeField] public VisualElement SlotElement { get; set; }
[field: SerializeField] public Chest ChestInSlot { get; set; }
[field: SerializeField] public string CurrentTime { get; set; }
[field: SerializeField] public bool ChestCanBeOpened { get; set; }
[field: SerializeField] public int Hours { get; set; }
[field: SerializeField] public int Minutes { get; set; }
[field: SerializeField] public int Seconds { get; set; }
[field: SerializeField] public IntVariable HoursTimer { get; set; }
[field: SerializeField] public IntVariable MinutesTimer { get; set; }
[field: SerializeField] public IntVariable SecondsTimer { get; set; }
}
Result
You want the timer to be different for each one?
That should work if they have their own variables like you did originally. What does the code look like when you do that?
That’s the problem it seems like IntVariable act as a static variable even if it’s isolated by each instance.
The thing is
In the image above I showed that each Element0 and Element1 have Hours, Minutes and Seconds that preset the timer for each instance to IntVariable HoursTimer etc… so techinally it’s like this:
slot.HoursTimer.Value = slot.Hours;
slot.MinutesTimer.Value = slot.Minutes;
slot.SecondsTimer.Value = slot.Seconds;
then here’s the method responsible to separate each instance. I have left some comments above to make you understand what operation does:
public void ShowChestInfo(ChestSlot slot, Chest chest)
{
if (rootChestInfo == null)
{
DebugManagerInEditor.LogWarning("Chest Info UI not initialized!");
return;
}
// Make the dialog visible
rootChestInfo.Q("OpenDialogChest").style.display = DisplayStyle.Flex;
// Update dialog UI with chest details
rootChestInfo.Q<Label>("chestNameText").text = chest.ChestName.GetLocalizedString();
// Example: Add rewards list dynamically
var rewardsContainer = rootChestInfo.Q("LootWindow");
rewardsContainer.Clear(); // Clear old rewards
if (string.IsNullOrEmpty(slot.CurrentTime))
{
if (chest.ChestRarity == ChestRarity.Common)
{
slot.Hours = 0;
slot.Minutes = 15;
slot.Seconds = 0;
slot.CurrentTime = string.Empty;
slot.ChestCanBeOpened = false;
}
else if (chest.ChestRarity == ChestRarity.Rare)
{
slot.Hours = 1;
slot.Minutes = 30;
slot.Seconds = 0;
slot.CurrentTime = string.Empty;
slot.ChestCanBeOpened = false;
}
else if (chest.ChestRarity == ChestRarity.Legendary)
{
slot.Hours = 8;
slot.Minutes = 30;
slot.Seconds = 30;
slot.CurrentTime = string.Empty;
slot.ChestCanBeOpened = false;
}
var openChestBtn = rootChestInfo.Q<Button>("OpenChest");
// My own class that responsible for registration callbacks so I can generally unsubscribe from all
//of them once it destroyed or not used anymore it's act as dictionary
ElementManagerRegister.RegisterCallback<ClickEvent>(openChestBtn, evt => StartTimerChest(evt, slot));
foreach (var reward in chest.Rewards)
{
var prizes = chestPrize.Instantiate();
prizes.Q("IconPrize").style.backgroundImage = new StyleBackground(reward.iconSprite);
prizes.Q<Label>("PrizeText").text = reward.nameItem.GetLocalizedString();
ElementManagerRegister.RotateEffects(prizes, "ShinyAnim", speed: 7.5f);
rewardsContainer.Add(prizes);
}
TimerCounter = rootChestInfo.Q<Label>("SetupTimeText");
var source = LocalizationSettings.StringDatabase.SmartFormatter.GetSourceExtension<PersistentVariablesSource>();
slot.HoursTimer = source["global"]["hours"] as IntVariable;
slot.MinutesTimer = source["global"]["minutes"] as IntVariable;
slot.SecondsTimer = source["global"]["seconds"] as IntVariable;
slot.HoursTimer.Value = slot.Hours;
slot.MinutesTimer.Value = slot.Minutes;
slot.SecondsTimer.Value = slot.Seconds;
var localizedString = new LocalizedString("General UI Text", "key_timer");
TimerCounter.text = localizedString.GetLocalizedString();
// store the current building information when dialog pop-up
selectedVisual = slot.SlotElement;
}
}
This one act as a Timer start for each slot
private void StartTimerChest(ClickEvent evt, ChestSlot slot)
{
TimePrepare(slot).Forget();
}
private async UniTask TimePrepare(ChestSlot slot)
{
slot.ChestCanBeOpened = false;
var chestOpeningTime = new TimeSpan(slot.Hours, slot.Minutes, slot.Seconds);
var timeRemaining = chestOpeningTime;
// Retrieve the trusted server time only once
if (string.IsNullOrEmpty(slot.CurrentTime))
{
//startBuildingConstruction is a DateTime variable
startBuildingConstruction = await GetTimeNet.GetNet(); // Get the server time once at the start it's act as a normal DateTime
slot.CurrentTime = ZString.Format("{0}", startBuildingConstruction);
}
// Calculate the expected end time based on server time
DateTime expectedEndTime = startBuildingConstruction + chestOpeningTime;
float countdownStartTime = Time.unscaledTime; // Using unscaled time to avoid system time manipulations
var elapsedTime = TimeSpan.Zero; // Start with 0 elapsed time
// Set a fixed delay duration (1 second)
float delayDuration = 1.0f;
float lastUpdateTime = countdownStartTime;
// Start the countdown without calling GetTimeNet repeatedly
while (timeRemaining > TimeSpan.Zero)
{
// Calculate the elapsed time based on unscaled time (not affected by time changes)
float elapsedSinceStart = Time.unscaledTime - countdownStartTime;
// Convert elapsed time to TimeSpan for calculations
elapsedTime = TimeSpan.FromSeconds(elapsedSinceStart);
// Calculate the remaining time based on expected end time and elapsed time
timeRemaining = expectedEndTime - (startBuildingConstruction + elapsedTime);
// If time has elapsed, break the loop
if (timeRemaining <= TimeSpan.Zero)
{
timeRemaining = TimeSpan.Zero;
break;
}
// Update UI only if a full second has passed since the last update
if (Time.unscaledTime - lastUpdateTime >= delayDuration)
{
slot.HoursTimer.Value = timeRemaining.Hours;
slot.MinutesTimer.Value = timeRemaining.Minutes;
slot.SecondsTimer.Value = timeRemaining.Seconds;
// Update last update time to avoid redundant UI updates
lastUpdateTime = Time.unscaledTime;
}
// Wait for the next second to pass before updating the timer
await UniTask.Delay(TimeSpan.FromSeconds(delayDuration));
}
// Timer finished, building can be opened
slot.ChestCanBeOpened = true;
slot.CurrentTime = string.Empty;
// Reset the start time to the latest server time after the timer is done
startBuildingConstruction = await GetTimeNet.GetNet();
}
Hi,
This sounds like it could be a bug, each element should have their own instance. Could you please file a bug report?
It doesn’t seems to be a bug for at least not for scripts part because without Localization it works as expected if I don’t translate letters and keep the timer ticking.
I’m not really sure what do you mean by element should have their own instance because without localization text it does work. It does separate elements to it’s own instances
If the variables are being shared across the elements then it sounds like a bug. Each variable should be it’s own instance, not shared.
It does only happens when the localization text is applied. So It relates to Localization bug definitely
Please file a bug report so we can investigate.