Hello,
Here is an idea of what I would like to achieve. This code lets you assign a cube mesh and a background material in the inspector (for the background of the, “Game settings” screen). The variable, “pLoadFile” is what is used to read and write game settings to a .txt file. That is where you tell it where the .txt file is located, but you must first create the .txt file and add the data of which variables you would like to see show up in the “Game settings” screen. The variable, pKey is the key used to bring up the “Game settings” screen and its GUI. pActive determines if the screen should be loaded or not. The variable could then be used to determine weather or not to pause everything else inside the game EXCEPT for the “Game settings” screen. The main directory for where the file gets loaded and saved is in the, “Assets\Resources” folder.
import System.Collections.Generic;
var pBackgroundMesh : Mesh;
var pBackgroundMaterial : Material;
var pLoadFile : String;
@HideInInspector
var pSettingsObj : GameObject;
@HideInInspector
var pDescListings : List.<String>;
@HideInInspector
var pNameListings : List.<String>;
@HideInInspector
var pValueListings : List.<String>;
@HideInInspector
var pDataListings : List.<String>;
var pKey : String;
@HideInInspector
var pActive : boolean;
function Start () {
getListings();
changeSettings();
var obj : GameObject = new GameObject();
obj.name = "Settings background";
obj.transform.parent = transform;
obj.transform.localPosition = Vector3(0,0,0.0101);
obj.transform.localEulerAngles = Vector3(0,0,0);
obj.transform.localScale = Vector3(1,1,0.0001);
obj.AddComponent(MeshFilter);
obj.GetComponent(MeshFilter).mesh = pBackgroundMesh;
obj.AddComponent(BoxCollider);
obj.GetComponent(BoxCollider).size = Vector3(1,1,1);
obj.AddComponent(MeshRenderer);
obj.renderer.materials[0] = pBackgroundMaterial;
pSettingsObj = obj;
pSettingsObj.SetActiveRecursively(false);
}
function Update () {
if (pKey != "") {
if (Input.GetKeyDown(pKey)) {
pActive = !pActive;
if (pActive && pNameListings.Count == 0) {
getListings();
}
if (pSettingsObj) {
pSettingsObj.SetActiveRecursively(pActive);
}
}
}
}
function doSettings() {
if (!pActive) {
if (pSettingsObj) {
pSettingsObj.SetActiveRecursively(true);
}
getListings();
pActive = true;
}
}
function getListings() : void {
pNameListings.Clear();
pValueListings.Clear();
pDataListings.Clear();
var textobj : Object = Resources.Load(pLoadFile, typeof(TextAsset));
var text : String = "";
if (textobj) {
text = textobj.ToString();
}
var des : String = "";
var nam : String = "";
var val : String = "";
var dat : String = "";
var i : int = 0;
var type : int = 0;
while (i < text.length) {
if (type == 0) {
if (text[i] != "|"[0]) {
des += text[i];
} else {
type = 1;
}
} else if (type == 1) {
if (text[i] != "="[0]) {
nam += text[i];
} else {
type = 2;
}
} else if (type == 2) {
if (text[i] != ":"[0]) {
val += text[i];
} else {
type = 3;
}
} else if (type == 3) {
if (text[i] != "\n") {
dat += text[i];
} else {
pDescListings.Add(des);
pNameListings.Add(nam);
pValueListings.Add(val);
pDataListings.Add(dat);
des = "";
nam = "";
val = "";
dat = "";
type = 0;
}
}
i++;
}
}
function OnGUI() {
if (pActive) {
var anchor : TextAnchor;
var currentStyle : GUIStyle = new GUIStyle( GUI.skin.box );
anchor = TextAnchor.UpperLeft;
currentStyle.wordWrap = true;
currentStyle.alignment = anchor;
GUI.skin.label.wordWrap = true;
var rect : Rect;
for (var i : int = 0; i < pNameListings.Count; i++) {
rect = Rect(0,(i*20),Screen.width/4,20);
GUI.Box(rect, pNameListings[i]);
rect = Rect(Screen.width/4,(i*20),Screen.width/4,20);
pValueListings[i] = GUI.TextArea(rect, pValueListings[i]);
}
rect = Rect(0,Screen.height-20,Screen.width/4, 20);
if (GUI.Button(rect, "Cancel")) {
if (pSettingsObj) {
pSettingsObj.SetActiveRecursively(false);
}
pActive = false;
}
rect = Rect(Screen.width-(Screen.width/4),Screen.height-20,Screen.width/4, 20);
if (GUI.Button(rect, "Save")) {
if (pSettingsObj) {
pSettingsObj.SetActiveRecursively(false);
}
saveSettings();
pActive = false;
}
}
}
function saveSettings() : void {
var w : StreamWriter = new StreamWriter("Assets/Resources/"+pLoadFile+".txt");
for (var i : int = 0; i < pNameListings.Count; i++) {
w.Write(pDescListings[i]+"|"+pNameListings[i]+"="+pValueListings[i]+":"+pDataListings[i]+"\n");
}
w.Close();
changeSettings();
}
function changeSettings() : void {
var i : int = 0;
var j : int = 0;
var obj : GameObject = null;
var comp : Component = null;
var first : boolean = true;
while (j < pDataListings.Count) {
i = 0;
obj = null;
var name : String = "";
first = true;
while (i < pDataListings[j].length) {
if (pDataListings[j][i] != "!"[0] && pDataListings[j][i] != "@"[0] && pDataListings[j][i] != "#"[0] && pDataListings[j][i] != "$"[0]) {
name += pDataListings[j][i];
} else {
if (pDataListings[j][i] == "!"[0] || pDataListings[j][i] == "@"[0]) {
if (first) {
obj = GameObject.Find(name);
} else {
if (obj.transform.Find(name)) {
obj = obj.transform.Find(name).gameObject;
}
}
first = false;
}
if (pDataListings[j][i] == "#"[0]) {
if (obj) {
comp = obj.GetComponent(name);
} else {
print("Object "+name+" not found within the hierarchy on line "+(i+1)+" in "+pLoadFile+".txt.");
}
}
if (pDataListings[j][i] == "$"[0]) {
if (obj) {
if (comp) {
if (int.Parse(name) < comp.Setting.Count) {
comp.Setting[int.Parse(name)] = pValueListings[j];
} else {
comp.Setting.Add(pValueListings[j]);
if (int.Parse(name) > comp.Setting.Count) {
print("Warning: "+pLoadFile+".txt does not have setting variables in order for the array stack.");
}
}
} else {
print("Component "+name+" not found on object "+obj.name+" on line "+(i+1)+" in "+pLoadFile+".txt.");
}
} else {
print("Object "+name+" not found within the hierarchy on line "+(i+1)+" in "+pLoadFile+".txt.");
}
}
name = "";
}
i++;
}
j++;
}
}
The format for the .txt file you create should be as follows with no spaces after or before each symbol…
(Your description) | (variable name to show) = (desired value) : (game object name) ! {(child object name) ! (child object name)} @ (component name) # (variable index) $ (new line)
Here is an example of a game setting file I designed that the code above can understand…
This sets the speed of the bug.|Speed=0.1:root object name !child object name!child object name@script name#0$
This sets the speed of the bug while you are interacting with the objects.|Interaction speed=0:bug@bug2#0$
This sets the multiplication of speed when the bug sees you.|Spot mult=1:bug@bug2#1$
This sets the increase of speed of the bug based on your level.|level inc=0.5:bug@bug2#2$
This sets the confusion of the bug when it can not see you.|Confusion=0.05:bug@bug2#3$
There is a problem though because I can not specify what variable in which script to change, so I designed the script above to look for an array of Strings inside each scripts that get changed, called “Settings”. That is where the, “variable index” inside the .txt file comes to play.
Then, it is the programmer’s responsibility to update the variables in their scripts to the variables inside their Settings list accordingly. Here is an example of one of my update functions that use the settings change…
var Settings : List.<String>;
var pSpeed : float;
var pInteractSpeed : float;
var pSpotSpeedMult : float = 1;
var pLevelSpeedIncrease : float;
var pGuess : float = 1;
...
function Update() {
if (Setting.Count >= 1) {
pSpeed = float.Parse(Setting[0]);
}
if (Setting.Count >= 2) {
pInteractSpeed = float.Parse(Setting[1]);
}
if (Setting.Count >= 3) {
pSpotSpeedMult = float.Parse(Setting[2]);
}
if (Setting.Count >= 4) {
pLevelSpeedIncrease = float.Parse(Setting[3]);
}
if (Setting.Count >= 5) {
pGuess = float.Parse(Setting[4]);
}
...
}
This is how I am able to easily create a “Game settings” screen, but wouldn’t it be neat if the next version of Unity had a built-in GUI program for that? What I’ve done so far ALSO deals with the issue behind the fact that StreamWriter doesn’t seem to deal with writing to Resource .txt files, so it would probably be a big issue waiting to happen when trying to publish a game that uses this script.