Save Settings

👍

Reminder - Keep Daily Backups!

When working on a strategy game with a kit as big as the Complete Kit - always keep a working daily backup! Save yourself the trouble of rolling-back changes and losing work.

Where are the game settings?

The kit comes with a few game settings for developers to test with including save and loading and sound effects.

This setting menu is located in the Game scene hierarchy > Anchor - Center group. The settings button is located in the Game scene hierarchy > Anchor - Bottom Right group.

In the actual game play, you can click the settings button to open the settings menu.

404

Settings button. Opens the settings menu. Click to view larger.

The following is a screenshot of the Game scene settings menu. We'll describe a few of these options further down on this page.

739

In-game developer settings menu. Click to view larger.

Why are there two rows of save buttons, File and Web?

Thanks to a customer request, we've added this feature for developers. File is the usual buttons you should use to save Player data to files. Most developers can ignore the Web column of save buttons which saves to PlayerPrefs.

Save TypeDescription
FileFile is local save to file. Useful for mobile, desktop, and console games.
WebWeb is player prefs save, and works on web players and web games.

Settings: Save Options

The first, and most useful section of the in-game settings allows you to load and save various base layouts either locally on your computer, or on the server (either the City Building Kit.com Demo server or your own server). Server save and load is required for gameplay battles against other players.

739

Save options. Click to view larger.

🚧

A player ID must be generated before you can perform a local or server load

Press "Local Save" to generate a Player ID or set your Player ID in the GameManager > Stats inspector. If you fail to do this, the Developer console will show an error message that no PlayerId exists.

Player ID is the unique map file name. For example if the Player ID is demouser then the map file will be saved like demouser.txt

Player ID is saved and retrieved locally here on your computer:
C:\Users\user\AppData\LocalLow\StartupKits\StrategyKit\MyMapID.txt

OptionDescription
Local Save (runs Scripts/Save/SaveLoadMap.cs)Saves the file locally on your device. For Unity development on Windows, it is this location: C:/Users/user/AppData/LocalLow/AStarterKits/StrategyKit/MyMapID.txt
For Mac, it is ~/Library (look for the unity AStrategyKit plist)
Local Load (runs Scripts/Save/SaveLoadMap.cs)First checks if the player data has already been loaded. Only one load per session is allowed (prevents collision errors from on top of an existing base). Then loads the player saved file from local save location mentioned above. If no file, an error returns in the developer console.
Server Save (runs script Scripts/Save/SaveLoadWWW.cs)First saves a copy locally on the device and then connects online to the server settings specified in the Game scene hierarchy GameManager > SaveLoadWWW Inspector and transmits. If the license code has not been added in the Inspector yet, then an error message will show. (see further down on this page)
Server Load (runs script Scripts/Save/SaveLoadWWW.cs)First checks if the player data has already been loaded. Only one load per session is allowed (prevents collision errors from on top of an existing base). Then connects online to the server settings specified in the Game scene hierarchy GameManager > SaveLoadWWW Inspector and asks for the player base file matching the Player ID. If no player ID exists, an error returns in the developer console.

About the Save/Load Scripts

ScriptDescription
SaveLoadMap.csSaves or loads the map data on the local device.
SaveLoadWWW.csTransfers the local map data to the server (included only with the Pro and Complete Kits)
SaveLoadBattle.csDownloads a player for battle or the battle results from the server (see Online Game Sync™ documentation)

What is a map ID?

Every player base must have a unique map ID and there's a part of both the SaveLoadMap.cs (for local saves) and SaveLoadWWW.cs (for server saves) that generates this if a map ID has not yet been created. The following is an excerpt of the script that performs that.

// The following script generates a random
// alphanumeric 10 digit map ID like X9aa6h3Ji1
//
private void GenerateMapid()
	{
		//generate a long random file name , to avoid duplicates and overwriting	
		string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
		char[] stringChars = new char[10];
				
		for (int i = 0; i < stringChars.Length; i++)
		{
			stringChars[i] = chars[Random.Range(0, chars.Length)];
		}
		
		myMapIDCode = new string(stringChars);	
		((Messenger)statusMsg).DisplayMessage("New map ID generated: "+ myMapIDCode);
	}

The difference between local and server options

There are two ways city building games can be played - either locally on your device or online against other players like Clash of Clans. For online gameplay, it requires a central server to coordinate between players.

Whether you want to have players interact with other player content is the only major difference between local and server options.

If you don't need players to interact with other players in your game, you can remove the server save/load options and the rest of the gameplay except for the battle's component (accessed through the competition battle menu).

However, remember that the server save component also allows you to coordinate player base between devices - allowing you to play on your phone and then pick up where you left off on your tablet. So really the use of these options depends on whether or not this is part of your game strategy.

Local Save/Load Option

Local load and local save options store the player data locally on the device. In the case of testing on Unity, that would be C:/Users/user/AppData/LocalLow/AStarterKits/StrategyKit/MyMapID.txt for Windows and ~/Library/ plist file for Mac.

Mobile devices have their own store locations too but unity automatically handles all of this conversion for you so you don't need to worry about the differences.

On the Menu, both of the button options for local saving are highlighted in the image below.

739

Local device storage testing. Click to view larger.

👍

Automatic Local Game Save / Loading

Please view Settings > Auto Save/Load Games to learn more about how to automatically save and load your games.

What scripts does Local Save and Local Load?

Scripts/Save/SaveLoadMap.cs is the script that performs everything for the Local Save and Local Load options, meanwhile Scripts/Save/SaveLoadWWW.cs is the script that controls Server Save and Server Load options.

The SaveGame() function in SaveLoadMap.cs is where all the player data from the session gets converted into saved data. Likewise

// The following function saves all the player data
// Used in SaveLoadMap.cs
private void SaveGame()
{
	((Stats)stats).gameWasLoaded = true;
  // saving the game also prevents the user from loading on top
  // we set this variable used by CheckLoadOnce() to prevent double loads

  // Output the save location for development purposes
	if (isFileSave) 
	{
		print ("Save Game Path on your system: " + filePath.ToString ());
		sWriter = new StreamWriter (filePath + fileNameLocal + fileExt);
	}
		else
	{
		print (	"Saving to PlayerPrefs.\n"+
		       	"PlayerPrefs are stored in HKEY_CURRENT_USER/SOFTWARE/StartupKits/StrategyKit (Run regedit)");
		savefileStrings.Clear ();

	}

  // Read all the structure data so we can write this into the player data
	ReadStructures();

  // First we write a line for the start of the file
	WriteNextLine ("###StartofFile###");

  // Then we begin by listing all the buildings and their grid coordinates
  
	WriteNextLine ("###PosStruct###");
	
	for (int i = 0; i < positionalStructures.Count; i++) 
	{
		Component sSel = positionalStructures[i].GetComponent("StructureSelector");
			
		WriteNextLine	(((StructureSelector)sSel).structureClass+","+ 	
						((StructureSelector)sSel).structureType+","+
		              	((StructureSelector)sSel).structureIndex.ToString()+","+
					  	((StructureSelector)sSel).grassType.ToString()+","+
						positionalStructures[i].transform.position.x+","+
						positionalStructures[i].transform.position.y
		              );

	}
  
  // Here we go through the player walls (if any) they have constructed
  // And what grid coordinates they are on

	WriteNextLine ("###GridStruct###");

	for (int i = 0; i < gridStructures.Count; i++) 
	{					
		Component sSel = gridStructures[i].GetComponent("StructureSelector");

		WriteNextLine	(((StructureSelector)sSel).structureClass+","+  
						((StructureSelector)sSel).structureType+","+
						((StructureSelector)sSel).structureIndex.ToString()+","+
						((StructureSelector)sSel).iRow.ToString()+","+
						((StructureSelector)sSel).jCol.ToString()
		);
	}
  
  // Now we store any details about active construction timers
  // Buildings the player may be waiting to finish
  // On load, we can track the time they started versus the current time
  // to automatically finish the construction if they've been gone long enough
  // from their game.

	WriteNextLine ("###Construction###");
	for (int i = 0; i < Construction.Length; i++) 
	{
		Component cSel = Construction[i].GetComponent("ConstructionSelector");
		WriteNextLine   (((ConstructionSelector)cSel).structureClass+","+
						((ConstructionSelector)cSel).structureType+","+
			            ((ConstructionSelector)cSel).constructionIndex.ToString()+","+
						((ConstructionSelector)cSel).grassType.ToString()+","+
			            ((ConstructionSelector)cSel).buildingTime+","+
			            ((ConstructionSelector)cSel).remainingTime+","+
			            ((ConstructionSelector)cSel).storageAdd+","+									            
						((ConstructionSelector)cSel).iRow+","+
						((ConstructionSelector)cSel).jCol+","+							
						
						Construction[i].transform.position.x+","+
						Construction[i].transform.position.y	
		              );
	}

  // Next, we will store the trees and other objects
  // and their locations. These are recorded with the player data
  // as every map is unique with the removables.
  
	WriteNextLine ("###Removables###");

	for (int i = 0; i < Removables.Length; i++) 
	{
		Component rSel = Removables[i].GetComponent("RemovableSelector");
			
		WriteNextLine (((RemovableSelector)rSel).removableType+","+
		              ((RemovableSelector)rSel).removableIndex.ToString()+","+
		              ((RemovableSelector)rSel).inRemoval.ToString()+","+
		              ((RemovableSelector)rSel).iColumn.ToString()+","+
		              ((RemovableSelector)rSel).jRow.ToString()
		              );
	}
  
  // Next we'll store the removable timers
  // These are any active construction projects for removing
  // trees, rocks, or other objects

	WriteNextLine ("###RemovableTimers###"); 

	for (int i = 0; i < RemovableTimers.Length; i++) 
	{
		Component rTimer = RemovableTimers[i].GetComponent("RemovableTimerSelector");
			
		WriteNextLine (((RemovableTimerSelector)rTimer).removableIndex.ToString()+","+
		              ((RemovableTimerSelector)rTimer).remainingTime.ToString());
	}

  // Next, we'll store the unique structure IDs for objects
  // The player has
  
	WriteNextLine ("###Numerics###");//the unique id for buildings/grass patches
	
	WriteNextLine (((StructureCreator)buildingCreator).structureIndex.ToString()+","+
		((StructureCreator)wallCreator).structureIndex.ToString()+","+
		((StructureCreator)weaponCreator).structureIndex.ToString());		                     
	// And here we store the units the player is training
  // Or already has in their population
  // We dump everything inside the units array
	WriteNextLine (((UnitProc)unitProc).currentSlidVal.ToString("0.00")+","+
	                   (((UnitProc)unitProc).currentTrainingTime)		                            
	                   );
	const int numberOfUnits = 10;
	int[] trainingTimes = new int[numberOfUnits];//an array that holds training times from all units - 
	//at first load, the XML will not have been read 
	int[] sizePerUnit = new int[numberOfUnits];
	int[] existingUnits = new int[numberOfUnits];//these units form the current population

	
	trainingTimes = ((UnitProc)unitProc).trainingTimes;//replace our empty array with the xml values, already in unitproc
	sizePerUnit = ((UnitProc)unitProc).sizePerUnit;
	existingUnits = ((Stats)stats).existingUnits;
			
	WriteNextLine (String.Join(",", new List<int>(trainingTimes).ConvertAll(i => i.ToString()).ToArray()));
	WriteNextLine (String.Join(",", new List<int>(sizePerUnit).ConvertAll(i => i.ToString()).ToArray()));
	WriteNextLine (String.Join(",", new List<int>(existingUnits).ConvertAll(i => i.ToString()).ToArray()));
		
	List<Vector3> queList = new List<Vector3>();
	queList=((UnitProc)unitProc).queList;
	
	for (int i = 0; i < queList.Count; i++) 
	{
		WriteNextLine(queList[i].ToString().Trim(new Char[] { ')', '(' }));
	}

  // Lastly any other stats we want to save
  // And game settings
  // You can add extra game settings here unique to your game
  
	WriteNextLine ("###Stats###");
	
	WriteNextLine (((Stats)stats).structureIndex+","+
				  ((Stats)stats).experience+","+
	              ((Stats)stats).dobbits+","+
	              ((Stats)stats).occupiedDobbits+","+
	              ((Stats)stats).occupiedHousing+","+
	              ((Stats)stats).maxHousing+","+
	              ((Stats)stats).gold+","+
	              ((Stats)stats).mana+","+
	              ((Stats)stats).crystals+","+
	              ((Stats)stats).maxGold+","+
	              ((Stats)stats).maxMana+","+
	              ((Stats)stats).maxCrystals+","+		           
	              ((Stats)stats).tutorialCitySeen+","+
	              ((Stats)stats).tutorialBattleSeen+","+
	              ((Stats)stats).removablesCreated+","+
	              ((SoundFX)soundFX).soundOn+","+
				  ((SoundFX)soundFX).ambientOn+","+
				  ((SoundFX)soundFX).musicOn
	              );
	
	WriteNextLine (System.DateTime.Now.ToString());
	
	WriteNextLine ("###EndofFile###");

  	// Write and close the file
	if (isFileSave) 
	{
		sWriter.Flush ();
		sWriter.Close ();
	}
	else
	{
		savefile = "";

		for (int i = 0; i < savefileStrings.Count; i++) 
		{
			savefile += savefileStrings[i]+"\n";
		}

		PlayerPrefs.SetString("savefile", savefile);
		PlayerPrefs.Save ();
	}
	existingBuildings = new int[buildingTypesNo];
  //reset for next save - remove if automatic
  
  // Update with the confirmation message
	((Messenger)statusMsg).DisplayMessage("Game saved.");
}

Here is an example of what is saved by the SaveGame() function

###StartofFile###
###PosStruct###
Weapon,Cannon,118,2,-384,362
Building,Barrel,119,3,-128,633.5
Building,Barrel,120,3,-768,181
###GridStruct###
WoodFence,WoodFenceNE,93,22,15
WoodFence,WoodFenceNE,94,22,16
WoodFence,WoodFenceNE,95,22,17
WoodFence,WoodFenceNE,96,22,18
WoodFence,WoodFenceNE,97,22,19
WoodFence,WoodFenceNE,98,22,20
###Construction###
###Removables###
TreeA,0,0,2,30
ClamA,2,0,3,4
ClamA,3,0,3,6
TreeA,4,0,3,7
###RemovableTimers###
###Numerics###
120,117,118
0.00,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
###Stats###
120,342,1,0,0,100,496400,499900,6696,500000,505000,7502,True,True,True,True,True
11/10/2016 9:35:58 PM
###EndofFile###

What about failsafes to prevent loading collision errors?

Only one load per session is allowed. The function CheckLoadOnce() runs during loading to ensure that you have not already loaded a base during testing. At the start of SaveGame() it sets the boolean stat gameWasLoaded to true to prevent loading another base.

Naturally, production games wouldn't encounter collision errors since you would only be loading on game launch if you follow the instructions in the Activate Auto Game Save / Load documentation page - but when testing you most likely won't have enabled auto save and load yet. There are times where it's easy to forget you've already loaded a map, or pressed the "Trees" button and have initiated trees already into the environment. In both cases - loading a map a second time would cause collision errors as objects would be placed possibly on top of other objects using the same grid coordinates.

// Checks that a game has not already been loaded
  // To prevent collision errors from loading a different map with 
  // objects on top of other existing objects
	private bool CheckLoadOnce()
	{
		bool gameWasLoaded = ((Stats)stats).gameWasLoaded;

		if(gameWasLoaded) //prevents loading twice, since there are no safeties and the procedure should be automated at startup, not button triggered
		{
			((Messenger)statusMsg).DisplayMessage("Only one load per session is allowed. Canceling...");
		}

		return gameWasLoaded;
	}

Server Save/Load

Technically, Server Save performs the same action as local save does, to generate a copy to upload to the server first before uploading it.

Server Load also does the same as the local load option, but first it downloads an updated copy of the player data from the server before loading the updated local copy.

If you have any trouble pressing the Server Save option, make sure you first pressed the Local Save option before pressing Server Save (which requires a local save file first to work)

739

Server testing. Click to view larger.

These options allow developers like you to test Player save data across devices and sessions.

👍

Automatic Server Game Save / Loading

Please view Settings > Auto Save/Load Games to learn more about how to automatically save and load your games.

How to test Server Load / Server Save

  1. Make sure you've already setup your license code in the Game scene hierarchy GameManager > SaveLoadWWW Inspector license field (see the last steps in the installation instructions page of this further documentation on how to add your license code to activate the Demo Server usage)

  2. Run the Game scene in Unity, design your city village and press Server save to store a copy of your base on the City Building Kit demo server. If you get an error about no Player ID existing, press local save first. It will generate a new Player ID, then press Server save. Make sure to record the Player ID (you'll see it in GameManager > SaveLoadWWW Inspector under the Player ID variable)

  3. Stop the scene. Then restart it. You'll be back at a blank map. Open the settings panel and press Server Load to download the copy you saved earlier. If you get an error about no existing player ID, make sure you save the same ID you recorded from the earlier step #2 into the GameManager > SaveLoadWWW Inspector.

What script manages the Server Save and Loading?

The following code is an excerpt from the Scripts/Save/SaveLoadWWW.cs which takes the player save data file created by SaveGame() and now uploads it to the server.

405

Developer console messages appear during server saving.

For compatible development purposes, we've designed this as a simple WWW post that all developers can understand but there are far more different options and exotic options you can experiment with depending on your development experience and server environment.

The following scripting will work with any and all web hosts. If you haven't already, make sure to login to CityBuildingKit.com/download and get the demo server PHP scripts for testing on your own web hosting account.

/*
 The following is an excerpt from the SaveLoadWWW.cs
 UploadPlayerPrefsLevel() function
 This demonstrates the WWW upload of the player's data to
 the server scripts.

	- you save the generated ID, then retrieve the uploaded file, without the needs of listings
	- the same method is used to retrieve and validate any uploaded file
	- this method is similar to the one used by the games like Bike Baron
	- saves you from the hassle of making complex server side back ends which enlists available levels
	- you could enlist outstanding levels just by posting the level code on a server 
	- easier to share, without the need of user accounts or install procedures

Use as an example for development or customize the server uploading for your own development needs.
*/
	// Begin a WWW post
	WWWForm form = new WWWForm();
	
	form.AddField("savefile","file");
	form.AddBinaryData( "savefile", levelData, myMapIDCode,"text/xml");//file
	
// change the url to the url of the server php file in GameManager > SaveLoadWWW
// posts the player data as savefile variable
	WWW w = new WWW(serverAddress + filesAddress +"?mapid=" + myMapIDCode+"&license="+license, form);//myUseridFile 
	
	yield return w;
	
// Report an error if there was a problem posting
	if (w.error != null)
	{
		((Messenger)statusMsg).DisplayMessage("File upload error.");
		print ( w.error );    
	}
	else
	{
    
		//this part validates the upload, by waiting 5 seconds then trying to retrieve it from the web
		//http://docs.unity3d.com/ScriptReference/WWW-uploadProgress.html

		if(w.isDone)
		{
			yield return new WaitForSeconds(5);
			// does the same as above, but this time calls
      // ?get_user_map=1 which returns the player's map data
			WWW w2 = new WWW(serverAddress + filesAddress + "?get_user_map=1&mapid=" + myMapIDCode+"&license="+license);//returns a specific map
			
			yield return w2;
			
      // Return an error message if there was a problem
			if(w2.error != null)
			{
				((Messenger)statusMsg).DisplayMessage("Server file check error.");
				print ( w2.error );  
			}
			else
			{
				// then if the retrieval was successful
        // validate its content to ensure the map file integrity is intact
        // We do this by verifying the start and end of the file saved
				if(w2.text != null && w2.text != "")
				{
					if(w2.text.Contains("###StartofFile###") && w2.text.Contains("###EndofFile###"))
					{
						//and finally announce that everything went well
						print ( "Received verification file map ID " + myMapIDCode + " contents are: \n\n" + w2.text);
						
						if(!serverSaveExists) 
						{
							SavePlayerPrefsMapid();
              // if for some reason, the web server doesn't 
              // reach this point in the verification
              // this function saves the player ID
						}
						
						((Messenger)statusMsg).DisplayMessage("Server PlayerPrefs upload complete.");
					}
					else
					{
            // In the event of an error
            // Prints the debug information in the Console
            // So you can find out what the server received
            // Most likely if you're using our demo server
            // You may only get this as a reminder to include your license
            // In the GameManager > SaveLoadWWW Inspector
            
						print ( "Map file " + myMapIDCode + " is invalid");
						print ( "Received verification file " + myMapIDCode + " contents are: \n\n" + w2.text);
            //although file is broken, prints the content of the retrieved file
						
						((Messenger)statusMsg).DisplayMessage("Server PlayerPrefs upload incomplete. Try again.");
					}
				}
				else
				{
					print ( "Map file " + myMapIDCode + " is empty");
					((Messenger)statusMsg).DisplayMessage("Server upload check failed.");
				}
			}
			
			
		}     
	}

Getting a download corrupt error with server save and server load?

If you're using the City Building Kit demo server, make sure you've entered your license code in the Game scene GameManager > SaveLoadWWW and Map01 scene GameManager SaveLoadBattle Inspectors. The license code is required to use the City Building Kit demo server. You can request your license code from support by email at: help at citybuildingkit.com

275

Error message when your license code has not yet been entered.

How to automate player game loading and saving on app start / quit?

We've kept automatic load and save options separate for developers to test every game play as if you were a new user. When you're ready to publish your game - you want to enable automatic save and loading. Please go to the section of the documentation on the left bar under Settings > Auto Save/Load Games to learn more about how to automatically save and load your games.

Sound Controls

The last options in the Settings menu control sounds. These sound settings are simple boolean on / off and trigger either UI sounds. Music triggers fade in and fade out of the music loop meanwhile Ambient and Sound settings are activated immediately.

739

Sound options. Click to view larger

ButtonDecsription
Sounds UI sound effects like button clicks.
Ambient Ambient sounds like birds or rain. Read the Ambient Sound Effects documentation page for more details.
Music Game music loop. Read the Game Music documentation for more details.

Sound option settings are remembered between player sessions

All sound settings are saved with the player game options in the Scripts/Save/SaveLoadMap.cs SameGame() function near the end, so when a player disables any one of these sound settings and then saves their game - their personalized sound settings will be remembered for the next time they load their game.

Game Exit

The last button in the game settings called Exit exits the application when pushed. The function ExitGame in Scripts/Menus/Main/MenuMain.cs is called by this button.

An excerpt of that function is included below:

public void ExitGame()
	{
		Application.Quit();
	}

❗️

Exit only works in builds

The exit button will not work in the Unity editor, nothing will happen. But when testing a release file (either a computer build or as a mobile app) the exit button works.

739

Exit closes the game. (Doesn't work in Unity editor but works in production)

How to remove buttons from the game settings menu

  1. In the Game scene hierarchy, open the UIAnchor/Anchor - Center and activate the Settings menu
  2. Disable the save and load buttons
  3. Deactivate the Settings menu
1000

A closeup of the Pro Kit options. Complete Strategy Kit is similar, except for the button names. Click on image to view larger