Hidden Soulmates is a hidden objects minigame you can play directly in your browser, completely free. In the game, you’ll be presented with a Valentine’s-themed scene filled with various images. Some of these images form pairs, and your goal is to find and click on those pairs to remove them from the scene. Completing Hidden Soulmates should only take a few minutes.
Built with Unity 6, Hidden Soulmates is designed to load fine in most browsers, though loading times may vary depending on your internet connection. It’s important that your browser is updated (it must support WebGL 2).
Creating this minigame was a fun break from the larger project we’re currently developing. We enjoyed every step of the process, from designing the Valentine’s scene to fine-tuning the gameplay with help from some friends.
Happy Valentine’s Day! May your day be filled with love, laughter, and maybe a little hidden object fun 🙂
Hello! Spooky Dwellers 1 on Steam has just leveled up! The game now fully supports the Czech language, making it more accessible and enjoyable for our Czech-speaking players. Whether you’re a new adventurer or a returning player, you can now dive into the spooky world in English or Czech!
This localization update wouldn’t have been possible without the help of a dedicated player from the Steam community, who generously volunteered his time and expertise to translate the game from English into Czech, and we’re so grateful for his contribution. Thank you for helping us bring Spooky Dwellers 1 to even more players!
So, what are you waiting for? Immerse yourself in the eerie adventures that await: puzzles, secrets, or just enjoying the spooky Match 3’s levels! We hope you love playing Spooky Dwellers 1!
In our Unity projects, we handle localization using the No Such Localization component, as detailed in a previous post. Specifically, we use the phrases version, where phrases act as keys to retrieve strings from a comprehensive table. Switching languages is as simple as changing the table for text (for images, we use a different method).
We maintain a large database of strings and extract a subset tailored to each game’s specific requirements. To do this, we compile a list of all the phrases used by the localization components within a given game. Our process is straightforward: we open each scene in the Unity project, retrieve all localization components, and store their phrases.
The following code demonstrates our approach. We’ve integrated this functionality into a Unity editor menu option, automating the entire process of retrieving phrases and writing them into a text file.
public class IKIGamesEditorTools
{
private const string LocalizationMenuPath = "IKIGames/Localization/Extract All Phrases";
private const string LocalizationFileName = "localization_keys.txt";
[MenuItem(LocalizationMenuPath)]
private static void ExtractAllLocalizationPhrasesInAllScenes()
{
HashSet<string> phrases = new HashSet<string>();
List<string> scenePaths = GetScenePaths();
foreach (var scenePath in scenePaths)
{
ExtractPhrasesFromScene(scenePath, phrases);
}
WritePhrases(phrases, LocalizationFileName);
}
private static List<string> GetScenePaths()
{
List<string> scenePaths = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
scenePaths.Add(scene.path);
}
return scenePaths;
}
private static void ExtractPhrasesFromScene(string scenePath, HashSet<string> phrases)
{
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(scenePath, UnityEditor.SceneManagement.OpenSceneMode.Single);
ExtractPhrasesFromLocalizationComponentes(phrases);
// No need to close the new scene since OpenSceneMode.Single opens it as the only active scene
Debug.Log($"Processed: {scenePath}");
}
private static void ExtractPhrasesFromLocalizationComponentes(HashSet<string> phrases)
{
foreach (NoSuchStudio.Localization.Localizers.TMProTextLocalizer localizer in Resources.FindObjectsOfTypeAll<NoSuchStudio.Localization.Localizers.TMProTextLocalizer>())
{
if (!string.IsNullOrEmpty(localizer.phrase))
{
phrases.Add(localizer.phrase);
}
}
foreach (NoSuchStudio.Localization.Localizers.TextLocalizer localizer in Resources.FindObjectsOfTypeAll<NoSuchStudio.Localization.Localizers.TextLocalizer>())
{
if (!string.IsNullOrEmpty(localizer.phrase))
{
phrases.Add(localizer.phrase);
}
}
}
private static void WritePhrases(HashSet<string> hashSet, string filename)
{
string filePath = Path.Combine(Application.dataPath, filename);
try
{
using (StreamWriter writer = new StreamWriter(filePath))
{
foreach (string item in hashSet)
{
writer.WriteLine(item);
}
}
Debug.Log($"Phrases written successfully to: {filePath}");
}
catch (System.Exception ex)
{
Debug.LogError($"Failed to write phrases to file: {ex.Message}");
}
}
}
Our current project is the localization of Nocturnarya Collector’s Edition. As usual, our target languages are: German, French, Dutch, Spanish, Portuguese and Italian. It’ll be a Windows-only release. This localization is a daunting task, which comprises a few intricate parts:
The Story: Nocturnarya’s story is a bit long, as the game builds a particular vampire lore, with adventure, scheming, military invaders, treasons and loyalty. The game portrays several characters of which the most important is you, the player. At its core, it’s a battle between you and Grysmore, the boss of the human invaders.
The vampire and the vampiress: In the original English game, you can choose how you want to be referred to as the story progresses: as a vampire, a vampiress, or a rockstar renegade. Localizing this is tricky, because of articles and genre-specific details in all the target languages. We’ll be removing the rockstar renegade option, and will do our best to keep the vampire and vampiress options. However, if it proves to be too error-prone we’ll just scrap this option altogether.
The Village: The game has a section that allows you to build a 3D village for your vampire tribe. However, the help window and the tutorials amount to a lot of text to translate.
The Match 3 levels: This includes all the help texts, instructions and user interface of the Match 3 levels.
Collector’s Edition goodies: Last but not least, we’ll have to translate the extra stories and minigames offered as Collector’s Edition exclusive bonuses.
If everything goes smoothly, this localization should be completed by the end of this month.
Localization in Unity with No Such Localization is easy and truly convenient. We used that package to localize our game 10th Corpse into several languages, and it was a straightforward process. When the time came to localize our game, we evaluated the localization options available for Unity. The canon choice, so to speak, is Unity’s own Localization package which, however, required us to update our project and also, at that time, did not have an expedited installation (it had to be installed manually.) In the long run, Unity’s Localization package is likely to become the standard and the first choice of Unity developers, but for now it’s still at the preview stage.
For 10th Corpse, however, No Such Localization was a wise choice. In fact, No Such Localization offers much more than what we really needed to localize our game, which only required localization of strings. No images and no audio had to be localized in 10th Corpse, but if your project needs that, No Such Localization easily allows for it.
What is No Such Localization?
It’s a localization package for Unity, and it comes in 2 versions: Lite and Pro. Both versions are available in the Unity Asset Store. The Pro version (paid) offers even more features, such as support for Right-To-Left (RTL) languages (Hebrew, etc.), automatic variable replacement, and translation source classes for JSON and CSV files (more about this in a while.) Even the Lite version is awesome and, as told above, it offers much more than what our game’s localization required. No Such Localization integrates naturally with the Unity editor, and requires no coding unless you want to extend its functionality. And that’s the beauty of the package: its architecture, which makes it a breeze understanding and extending its functionality.
How to work with No Such Localization?
Let’s first understand the architecture of No Such Localization. The official documentantion is here and is very good. You’ll work with 3 classes of components. The most important of these components is the LocalizationService. It’s the core of your localization, and you should have a single instance in your scenes (ideally, use DontDestroyOnLoad to have single, persistent LocalizationService object if your project comprises multiple scenes.) Just create an empty object and append a LocalizationService component. Now, in the Locales list, add the languages you want to support, e.g., English and German. There are a few attributes, but you’ll be mostly changing Current Locale. Once you complete the localization process, modifying this Current Locale will immediately switch your project’s content to the localized strings and assets corresponding to such language.
So, main component: LocalizationService. We have yet to work with other two components: ComponentLocalizer and BaseTranslationSource. LocalizationService is our core, our hub, the ruler of all the localization of your game. However, it needs to know what scene’s objects (strictly speaking, components) it has to localize, and it also needs to know where it’s going to find the content with which it will localize those objects. For instance, let’s suppose you have a Text object (again, strictly speaking, an object with a Text component) displaying a “Welcome” string. The LocalizationService can automatically localize that object, but it needs to know that such object has to be localized (that’s the responsibility of ComponentLocalizer), and it needs to know what content it will use to localize the object (that’s the responsibility of BaseTranslationSource.) You can subclass both ComponentLocalizer and BaseTranslationSource to adapt them to the requirements of your project, but No Such Localization comes with classes that will suffice for basic requirements. For instance, the package includes a TextLocalizer component.
In continuation of our example, let’s add a TextLocalizer to our Text object having the “Welcome” string. You’ll notice that the TextLocalizer component has a Phrase attribute. When localizing, you’ll have to remove string literals (e.g., “Welcome”) from your components and replace them with unique IDs. The localization components will used those unique IDs to identify the content. Place unique IDs in that Phrase attribute, e.g., welcome_text. Now we need a Translation Source with an entry for that unique ID, welcome_text. The package comes with a very basic translation source, StandaloneTranslationSource which we can recur to for a simple use case like this. Create another object, add a StandaloneTranslationSource component, and edit its Translation List attribute to add welcome_text as an entry of such list, with translations for Locale English and Locale German.
Translation List of a Standalone Translation Source
That’s all. All the components of No Such Localization will connect among them automatically, and if you go back to your LocalizationService component and change the Current Locale you’ll see your text component modifying its content accordingly. It’s like magic!
Of course, if your project is medium or large sized, working with the StandaloneTranslationSource can be tiresome and error-prone. You might want to read your content from a file (the Pro version of this component comes with translation sources for JSON and CSV files.) The wonderful thing about architecture of No Such Localization is that you can extend it as you wish (we created our own Translation Source class for 10th Corpse.) And you can also create your own ComponentLocalizer for your specialized widgets, for example. You can localize text, images, sound, etc. Further information in the official documentation. An excellent tool for localizing your Unity project!
I’m using the fantastic DOTween engine for creating and managing tweens on Unity, but the latest version cannot handle a specific case: how to append a tween to a sequence which is already playing? First a bit of background. After completing DragonScales 6, we’re focusing on a new game, a casual Match 3 with a traditional tile swapping gameplay. Our DragonScales games are created with Java and LibGDX. However, this new project will be built on Unity with C#, and we make intensive use of tweens.
In particular, we want the players to be able to keep playing and swapping tiles even if the game is simultaneously processing matches in other areas of the board. When a match is detected, tiles above those matched tiles will fall. We’re implementing this falling path with position tweens, via DOTween. However, new matches in other areas might alter the path of tiles which are already falling. In order to handle such cases we’ll be appending new tweens to the tweens which are already executing. The problem is that sequences in the current version of DOTween must be entirelly defined beforehand, which clearly does not suit our requirements.
public class TweenChain
{
public Queue<Sequence> SequenceQueue = new Queue<Sequence>();
public TweenChain()
{
// empty
}
public void AddAndPlay(Tween tween)
{
// Create a paused DOTween sequence to "wrap" our tween
var sequence = DG.Tweening.DOTween.Sequence();
sequence.Pause();
// "Wrap" the tween
sequence.Append(tween);
// Add tween to queue
SequenceQueue.Enqueue(sequence);
// If this is the only tween in queue, play it immediately
if (SequenceQueue.Count == 1)
{
SequenceQueue.Peek().Play();
}
// When the tween finishes, we'll evaluate the queue
sequence.OnComplete(OnComplete);
}
private void OnComplete()
{
// Tween completed. Remove it.
SequenceQueue.Dequeue();
// Other tweens awaiting?
if (SequenceQueue.Count > 0)
{
// Play next tween in queue
SequenceQueue.Peek().Play();
}
}
public bool IsRunning()
{
// Are tweens being processed?
return (SequenceQueue.Count() > 0);
}
public void Destroy()
{
// Goodbye. Thanks for your hard work.
foreach (var sequence in SequenceQueue)
{
sequence.Kill();
}
SequenceQueue.Clear();
}
}
Hopefully future versions of DOTween will handle this use case in a straightforward fashion. We needed to append a tween to a sequence in execution and, for the time being, this class solves our requirement.