• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
  • entries
    34
  • comments
    61
  • views
    70079

A Unity PlayerPrefs wrapper

Sign in to follow this  
Followers 0
Gamieon

2061 views

Apologies for the terribly boring journal entry thumbnail image. Anyway...

With every new project I try to make better tools to carry into my future projects. One of them has to do with the local game configuration and PlayerPrefs. There are two issues I have with PlayerPrefs:


  • Using PlayerPrefs.Get... is slow in an Update() or an OnGUI() function (at least it was for me on mobile), and you have to grab the value in Awake() or Start() into a member variable and use that to avoid the issue.
  • No PlayerPrefs.GetBool() for yes/no toggling.

    So I decided to make my own static class that not only addresses both issues, but does so in a way that I find convenient for organization. I call this class the "ConfigurationDirector."


    The Preference Cache



    For those of you who learn by staring at code first, or just want to copy it into your project, here you go (C# version):public static class ConfigurationDirector { #region Preference Cache static Dictionary cachedFloatProps = new Dictionary(); static Dictionary cachedStringProps = new Dictionary(); static Dictionary cachedIntProps = new Dictionary(); public static float GetFloat(string prefName, float defaultValue) { if (!cachedFloatProps.ContainsKey(prefName)) { cachedFloatProps.Add(prefName, PlayerPrefs.GetFloat(prefName, defaultValue)); PlayerPrefs.SetFloat(prefName, PlayerPrefs.GetFloat(prefName, defaultValue)); } return cachedFloatProps[prefName]; } public static void SetFloat(string prefName, float newValue) { PlayerPrefs.SetFloat(prefName, newValue); if (!cachedFloatProps.ContainsKey(prefName)) { cachedFloatProps.Add(prefName, newValue); } else { cachedFloatProps[prefName] = newValue; } } public static string GetString(string prefName, string defaultValue) { if (!cachedStringProps.ContainsKey(prefName)) { cachedStringProps.Add(prefName, PlayerPrefs.GetString(prefName, defaultValue)); PlayerPrefs.SetString(prefName, PlayerPrefs.GetString(prefName, defaultValue)); } return cachedStringProps[prefName]; } public static void SetString(string prefName, string newValue) { PlayerPrefs.SetString(prefName, newValue); if (!cachedStringProps.ContainsKey(prefName)) { cachedStringProps.Add(prefName, newValue); } else { cachedStringProps[prefName] = newValue; } } public static int GetInt(string prefName, int defaultValue) { if (!cachedIntProps.ContainsKey(prefName)) { cachedIntProps.Add(prefName, PlayerPrefs.GetInt(prefName, defaultValue)); PlayerPrefs.SetInt(prefName, PlayerPrefs.GetInt(prefName, defaultValue)); } return cachedIntProps[prefName]; } public static void SetInt(string prefName, int newValue) { PlayerPrefs.SetInt(prefName, newValue); if (!cachedIntProps.ContainsKey(prefName)) { cachedIntProps.Add(prefName, newValue); } else { cachedIntProps[prefName] = newValue; } } public static bool GetBool(string prefName, bool defaultValue) { return (GetInt(prefName, (defaultValue) ? 1 : 0) == 0) ? false : true; } public static void SetBool(string prefName, bool newValue) { SetInt(prefName, newValue ? 1 : 0); } #endregion
    In short, what I do is call PlayerPrefs functions when I need values that are not cached in memory, and just read from memory at each subsequent Get. When I Set values however, I have to write to both cache and PlayerPrefs. Setting is, however, a fairly rare event.

    Doing a dictionary lookup at each frame is of course not as fast as just grabbing the preference value in Awake() or Start() into a member variable and using that member variable in Update() or OnGUI(); but I don't notice the speed hit even on an iPhone 3GS and I like knowing that the preference is always up to date so long as I use the ConfigurationDirector properly.


    Contexts



    So you need to track your player's name, their high score, the class they're using for the current game, the color of their uniform, the game difficulty level, the music volume, the IP address of the most recent server they played on....and that's just the beginning!

    I chose to bring order to that chaos by having my own system which I'll explain as a C# code snippet:public static class ConfigurationDirector { /// /// Player configuration settings /// public static class Player { /// /// Gets or sets the player name. /// /// /// The name. /// public static string Name { get { return GetString("Player.Name", ""); } set { SetString("Player.Name", value); } } /// /// Gets the default hue. /// /// /// The default hue. /// public static float DefaultHue { get { return 62f; } } /// /// Gets or sets the player hue. This is a floating precision number between and including /// 0 and 360. /// /// /// The player hue. /// public static float Hue { get { return GetFloat("Player.Hue", DefaultHue); } set { SetFloat("Player.Hue", value); } } /// /// Sets the color of the player. /// /// /// The color of the player. /// public static Color PlayerColor { get { return ColorDirector.HSL2RGB(Hue / 360.0, 0.7, 0.5); } } /// /// Gets or sets a value indicating whether this player has seen tutorial. /// /// /// true if this player has seen tutorial; otherwise, false. /// public static bool HasSeenTutorial { get { return GetBool("Player.HasSeenTutorial", false); } set { SetBool("Player.HasSeenTutorial", value); } } } /// /// Audio configuration settings /// public static class Audio { /// /// Gets the default SFX volume. /// /// /// The default SFX volume. /// public static float DefaultSFXVolume { get { return 0.5f; } } /// /// Gets or sets the SFX volume. /// /// /// The SFX volume. /// public static float SFXVolume { get { return GetFloat("Audio.SFXVolume", DefaultSFXVolume); } set { SetFloat("Audio.SFXVolume", value); } } /// /// Gets the default music volume. /// /// /// The default music volume. /// public static float DefaultMusicVolume { get { return 0.4f; } } /// /// Gets or sets the music volume. /// /// /// The music volume. /// public static float MusicVolume { get { return GetFloat("Audio.MusicVolume", DefaultMusicVolume); } set { SetFloat("Audio.MusicVolume", value); } } } /// /// Network configuration settings /// public static class NetworkSettings { /// /// Gets or sets the game name. /// /// /// The name. /// public static string GameName { get { return GetString("Network.GameName", "My Game"); } set { SetString("Network.GameName", value); } } /// /// Gets the default port. /// /// /// The default port. /// public static int DefaultPort { get { return 12345; } } /// /// Gets or sets the port. /// /// /// The port. /// public static int Port { get { return GetInt("Network.Port", DefaultPort); } set { SetInt("Network.Port", value); } } } /// /// Unlocks. /// public static class Unlocks { /// /// Determines whether unlocks are enabled /// /// /// True if unlocks are enabled /// public static bool Enabled { get { return GetBool("Unlocks.Enabled", false); } set { SetBool("Unlocks.Enabled", value); } } } }
    If I want to get the player's name for example, I do:

    string name = ConfigurationDirector.Player.Name;

    The call is generally ConfigurationDirector..

    The PlayerPrefs key name is generally "."

    Doing this has these advantages:

    • Developers can use Intellisense to look up existing preferences as they add new code instead of surfing around the project to find existing preference key strings.
    • Every preference key string is defined in a single file.
    • There is consistency between the preference key string and the call to get or set a preference (and therefore predictability); and I don't think the compiler would let you have any duplicate preference key strings if you follow that consistency.
    • If you're new to the project (or are going back to it after a while), it's a faster learning curve to implement new preference-related code than if there were PlayerPref calls scattered everywhere.


      Conclusion



      This is how I manage configurations in my recent games, and it works well for my purposes. If you're not happy with how you maintain your game's configuration, or you forgot the preference key for the player skin for the 8th time, it might be worth looking into doing something like this.

3
Sign in to follow this  
Followers 0


3 Comments


Thanks for sharing -- I'm not a Unity user, but it sounds like something people may find useful! :)

1

Share this comment


Link to comment

Most of the get/set methods have duplicated code. Why not use generics to define it once for all types?

0

Share this comment


Link to comment

Most of the get/set methods have duplicated code. Why not use generics to define it once for all types?

 

In the end, for me, it came down to coding style and level of importance. The short answer is: PlayerPrefs does not use generics.

 

If you call Get the first time, and the value is not in the cache, then you have to call PlayerPrefs.Get to pull the value into the cache. Assuming you're doing this in a function declared like "static int Get<T>(string prefName, T defaultValue)," you'd have to look at the type of T to figure out which PlayerPrefs.Get to call (I have not tried it but maybe you could cleverly use reflection or Invoke?). The same is true in the Set function to decide which PlayerPrefs.Set to call. If the type of T was unsupported, I would throw an exception. That should never happen; but there has to be a code path for such a case regardless.

 

That's where I thought "You know...I would rather duplicate a small number of code fragments than deal with generics, and I don't know a superior third alternative."

 

 

I would like to note that the way I did it does have a flaw, which is that I use a different dictionary for each type. That is not how Unity does it. If you call PlayerPrefs.SetString("mykey"), then the return value of PlayerPrefs.GetInt("mykey") is affected. I can't think of a good reason why one would call different PlayerPrefs get/set functions for the same key, though.

0

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now