Gamieon is a one-developer part-time studio focused on creating video games for the desktop and mobile platforms. Since 2010 Gamieon has developed and released six games as well as six more prototypes. Including beta distributions, Gamieon's products have netted a combined 300,000 downloads! Below this bio are images from all my games and prototypes. Check them out and don't forget to follow me on Twitter @[Gamieon](members:gamieon:321769)

Report RSS A Unity PlayerPrefs wrapper

Posted by on

With every new project I try to make better tools to carry into my future projects. One of them has to do with the 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:

csharp code:
public static class ConfigurationDirector
{
  #region Preference Cache
 
  static Dictionary<string,float> cachedFloatProps = new Dictionary<string, float="">();
  static Dictionary<string,string> cachedStringProps = new Dictionary<string, string="">();
  static Dictionary<string,int> cachedIntProps = new Dictionary<string, int="">();
 
  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;
    }
   
    if ("Audio.MusicVolume" == prefName)
    {
      AudioDirector.MusicVolume = 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:

csharp code:
public static class ConfigurationDirector
{

  /// <summary>
  /// Player configuration settings
  /// </summary>
  public static class Player
  {
    /// <summary>
    /// Gets or sets the player name.
    /// </summary>
    /// <value>
    /// The name.
    /// </value>
    public static string Name
    {
      get {
        return GetString("Player.Name", "");
      }
      set {
        SetString("Player.Name", value);
      }
    }
   
    /// <summary>
    /// Gets the default hue.
    /// </summary>
    /// <value>
    /// The default hue.
    /// </value>
    public static float DefaultHue { get { return 62f; } }

    /// <summary>
    /// Gets or sets the player hue. This is a floating precision number between and including
    /// 0 and 360.
    /// </summary>
    /// <value>
    /// The player hue.
    /// </value>
    public static float Hue
    {
      get {
        return GetFloat("Player.Hue", DefaultHue);
      }
      set {
        SetFloat("Player.Hue", value);
      }
    }
   
    /// <summary>
    /// Sets the color of the player.
    /// </summary>
    /// <value>
    /// The color of the player.
    /// </value>
    public static Color PlayerColor
    {
      get
      {
        return ColorDirector.HSL2RGB(Hue / 360.0, 0.7, 0.5);
      }
    }
   
    /// <summary>
    /// Gets or sets a value indicating whether this player has seen tutorial.
    /// </summary>
    /// <value>
    /// <c>true</c> if this player has seen tutorial; otherwise, <c>false</c>.
    /// </value>
    public static bool HasSeenTutorial
    {
      get {
        return GetBool("Player.HasSeenTutorial", false);
      }
      set {
        SetBool("Player.HasSeenTutorial", value);
      }
    }
  }
 
  /// <summary>
  /// Audio configuration settings
  /// </summary>
  public static class Audio
  {
    /// <summary>
    /// Gets the default SFX volume.
    /// </summary>
    /// <value>
    /// The default SFX volume.
    /// </value>
    public static float DefaultSFXVolume { get { return 0.5f; } }

    /// <summary>
    /// Gets or sets the SFX volume.
    /// </summary>
    /// <value>
    /// The SFX volume.
    /// </value>
    public static float SFXVolume
    {
      get {
        return GetFloat("Audio.SFXVolume", DefaultSFXVolume);
      }
      set {
        SetFloat("Audio.SFXVolume", value);
      }
    }
   
    /// <summary>
    /// Gets the default music volume.
    /// </summary>
    /// <value>
    /// The default music volume.
    /// </value>
    public static float DefaultMusicVolume { get { return 0.4f; } }
   
    /// <summary>
    /// Gets or sets the music volume.
    /// </summary>
    /// <value>
    /// The music volume.
    /// </value>
    public static float MusicVolume
    {
      get {
        return GetFloat("Audio.MusicVolume", DefaultMusicVolume);
      }
      set {
        SetFloat("Audio.MusicVolume", value);
      }
    }
  }
 
  /// <summary>
  /// Network configuration settings
  /// </summary>
  public static class NetworkSettings
  {
    /// <summary>
    /// Gets or sets the game name.
    /// </summary>
    /// <value>
    /// The name.
    /// </value>
    public static string GameName
    {
      get
      {
        return GetString("Network.GameName", "My Game");
      }
      set
      {
        SetString("Network.GameName", value);
      }    
    }
   
    /// <summary>
    /// Gets the default port.
    /// </summary>
    /// <value>
    /// The default port.
    /// </value>
    public static int DefaultPort { get { return 12345; } }
 
    /// <summary>
    /// Gets or sets the port.
    /// </summary>
    /// <value>
    /// The port.
    /// </value>
    public static int Port
    {
      get
      {
        return GetInt("Network.Port", DefaultPort);
      }
      set
      {
        SetInt("Network.Port", value);
      }
    }
  }
 
  /// <summary>
  /// Current level settings.
  /// </summary>
  public static class CurrentSession
  {
    /// <summary>
    /// Gets or sets the difficulty.
    /// </summary>
    /// <value>
    /// The difficulty.
    /// </value>
    public static LevelDifficulty Difficulty
    {
      get
      {
        return (LevelDifficulty)GetInt("CurrentSession.Difficulty", (int)LevelDifficulty.Normal);
      }
      set
      {
        SetInt("CurrentSession.Difficulty", (int)value);
      }
    }
   
    /// <summary>
    /// Determines whether we are in table top mode
    /// </summary>
    /// <value>
    /// True if we are in table top mode; otherwise false
    /// </value>
    public static bool InTableTopMode
    {
      get {
        return GetBool("CurrentSession.InTableTopMode", false);
      }
      set {
        SetBool("CurrentSession.InTableTopMode", value);
      }    
    }
  }
 
  /// <summary>
  /// Unlocks.
  /// </summary>
  public static class Unlocks
  {
    /// <summary>
    /// Determines whether unlocks are enabled
    /// </summary>
    /// <value>
    /// True if unlocks are enabled
    /// </value>
    public static bool Enabled
    {
      get
      {
        return GetBool("Unlocks.Enabled", false);
      }
      set
      {
        SetBool("Unlocks.Enabled", value);
      }    
    }
  }
 
}


If I want to get the player's name, I do:

string name = ConfigurationDirector.Player.Name;

Doing this has two advantages: No more remembering the key you assigned to that preference from outside the ConfigurationDirector, and you know from the context (Player) that it's a player-specific preference. I also know that anything following "PlayerPrefs.CurrentSession" applies to the game in progress, and anything following "PlayerPrefs.Audio" must be related to volume controls. I'll let auto-complete do the walking for me.

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 name for the 8th time, it might be worth looking into doing something like this.

Check out my homepage and social feeds

And my projects!

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: