Saturday, March 28, 2015

Implementing real-world real-time stamina / energy cooldown timers in Unity C#

In Hurt Me Plenty, I implemented "real-world" cooldown timers, which persist even if the player restarts the program. The cooldown period elapses in "real world" time, not in game time.

This resembles stamina delays in many popular free-to-play games, but it also connects with the design tradition of using real world system clocks to dictate game logic -- maybe certain Pokemon emerge at real world night, or you witness events that correspond with real world holidays, or perhaps you can even kill a boss NPC by setting your console's system clock forward by a week.

Much like the implementations referenced above, mine is quite weak and vulnerable to circumvention and cheating: I simply save a system timestamp in the game's PlayerPrefs, and then check that saved timestamp upon loading the game. If the difference between the current system time and the saved timestamp is less than zero, then the time has fully elapsed and the game continues.

WRITING A TIMESTAMP

First, make sure you're importing the System namespace at the top of your script file.

using System;  // import stuff we'll need for generating dates and times

Then you need to generate a DateTime value.

// set a timestamp of current time + 1 minute
var timeWhenCooldownFinishes = System.DateTime.Now.AddMinutes(1);

Then, encode that timestamp into a string, and save that data in PlayerPrefs.

// encode timestamp into binary, then into a string, then save into Unity PlayerPrefs
string dataString = timeWhenCooldownFinishes.ToBinary().ToString();
PlayerPrefs.SetString ( "Cooldown", dataString );
PlayerPrefs.Save ();

READING A TIMESTAMP

To recover a timestamp, load the string from PlayerPrefs and convert into a TimeSpan. Next, you'll want to measure the "time left" between the current system time and the saved timestamp, and then execute certain actions if enough time has elapsed.

// first, make sure such a PlayerPrefs key even exists
if (PlayerPrefs.HasKey ("Cooldown")) {
     cooldown = System.Convert.ToInt64(PlayerPrefs.GetString("Cooldown"));
     TimeSpan timeLeft = DateTime.FromBinary (cooldown).Subtract (System.DateTime.Now);
     Debug.Log("TIME LEFT:" + timeLeft.Seconds.ToString ("D2") + " seconds");
     if (timeLeft.TotalSeconds < 0) {  // if no time left, then begin the game!
         Debug.Log ("cooldown done!");
         BeginGame ();  // replace this function with whatever, etc.
     }
}

TIPS

When you import System, you might get errors about ambiguity between System.Random and UnityEngine.Random... personally, I prefer using UnityEngine.Random, so to fix it I put "using Random = UnityEngine.Random;" at the top. In general, the "using... = ..." syntax is very useful for specifying namespace shortcuts like that.

If you wanted a more secure implementation, you would have to encrypt the timestamp data somehow and/or store it on a server. Personally, I had no need for this -- if Hurt Me Plenty players want to go and literally delete the game's memory of their abuse, that's so fucked up that it's part of the art, and I feel that my point is made. Other games (Pokemon, Animal Crossing, Metal Gear Solid) don't really care if you "time travel" either, and even accommodate or expect that behavior.

***

Even though it is trivially bypassed by users who really want to bypass it, I believe real world cooldowns are extremely useful aesthetic tools to create artistic friction between player and game, to create either tension or release depending on how the delay is framed, as well as the duration of the delay.

Dominant game design paradigms generally privilege immediacy, and any barrier between player and game is supposedly "poor game design." In this case, as with most cases, "poor game design" is a codeword used to denigrate the "casual" play habits facilitated by many free-to-play games, or to support triple-A rhetoric emphasizing 60 frames-per-second immersive fidelity. Games can be about things other than immediacy!

"Refusing to play" is one of the most powerful actions available to players. Why not give machine players that same right?

For more on this design device / a different take, I recommend reading Zoya Street's mini-book "Delay", a history and critical analysis of energy mechanics.