Saving objects with FindObjectsOfTypeAll() - c#

I have a save-Method to save informations about every block with one of five specific tags, like position, health, rotation etc.
I first want to get all objects (even inactive ones), so i use the FindObjectsOfTypeAll()-Method.
Then i go with a foreach loop through all of the objects that have been found, and check if they have the right tag and if they do, i save them.
This is the code i use:
GameObject[] allObjects = Resources.FindObjectsOfTypeAll(typeof (GameObject)) as GameObject[];
using (StreamWriter write = new StreamWriter(dir + "blocksSave.dat"))
{
Debug.Log(allObjects.Length);
foreach (GameObject block in allObjects)
{
bool isActive = block.activeSelf;
block.SetActive(true);
if (block.tag == "WoodBlock" || block.tag == "WoodSteps" || block.tag == "WoodRamp" || block.tag == "GlasBlock" || block.tag == "WoodDoor")
{
// SAVE
write.WriteLine(block.tag + "," + block.transform.position.x + "," + block.transform.position.y + "," + block.transform.position.z + "," + block.GetComponent<BlockControl>().GetHealth().x + "," + block.GetComponent<BlockControl>().GetHealth().y + "," + block.transform.rotation.x + "," + block.transform.rotation.y + "," + block.transform.rotation.z);
}
block.SetActive(isActive);
}
write.Close();
}
I debugged this and the line if (block.tag == "WoodBlock" || ...) works fine.
The problem is the next line:
write.WriteLine(...);
here i get a nullReferenceException, where it tells me:
NullReferenceException: Object reference not set to an instance of an
object
And i can't figure out why?!

The issue is very probably that
block.GetComponent<BlockControl>()
returns null for one of your found GameObjects since it simply does not have that component.
Instead you could use
Resources.FindObjectsOfTypeAll<BlockCo troll>();
to be sure you only have all the components of type BlockControl in a separate list.
Than you can use a List to easier filter the objects for the tag instead of those || conditions
var searchedTags = new List<string>()
{
"WoodBlock",
"WoodSteps",
"WoodRamp",
"GlasBlock",
"WoodDoor"
};
if (searchedTags.Contains(block.gameObject.tag))
{
....
}
or even esier use Linq to only get the objects you are interested in:
var objectsOfInterest = allObjects.Where(obj => searchedTags.Contains(obj.gameObject.tag)).ToList();
I would also only use the StreamWriter for actually writing and do nothing else within that using block.
And note that if you are using a StreamWriter inside of a using block you don't have to use write.Close() since it is disposed automatically after the using block finishes.
So in complete I would use something like
var searchedTags = new List<string>()
{
"WoodBlock",
"WoodSteps",
"WoodRamp",
"GlasBlock",
"WoodDoor"
};
// This list is used to get all GameObject
// keep track which where active and activate all
var allObjects = Resources.FindObjectsOfTypeAll<GameObject>();
// Here we get all BlockController components
var allBlockObjects = Resources.FindObjectsOfTypeAll<BlockController>();
// finally we only get BlockControllers with the tags we want
var objectsOfInterest = allBlockObjects.Where(obj => searchedTags.Contains(obj.gameObject.tag)).ToList();
Debug.Log("objects total: " + allObjects.Length);
Debug.Log("block objects: " + allBlockObjects.Length);
Debug.Log("objects of interest: " + objectsOfInterest.Length);
// Step 1 keep track which GameObjects where active and activate all
var objectWasNotActive = new List<GameObject>();
foreach (var obj in allObjects)
{
if(!obj.activeSelf)
{
// keep track which objects where not active
objectWasNotActive.Add(obj);
}
// Set all to true
obj.SetActive(true);
}
// Step 2 save your BlockControllers with searched tags
using (StreamWriter write = new StreamWriter(dir + "blocksSave.dat"))
{
foreach (var block in objectsOfInterest)
{
// Here you already have components of type BlockController
// so you don't need to get them
// But GetHealth might also return null so maybe you want to check that too
// It is up to you whether you want to skip or fill it with default values in such case
var health = block.GetHealth();
if(health == null)
{
//option 1 skip
continue;
// OR option 2 default e.g.
// Assuming it is Vector2
health = Vector2.zero;
}
// SAVE
write.WriteLine(
block.gameObject.tag + ","
+ block.transform.position.x + ","
+ block.transform.position.y + ","
+ block.transform.position.z + ","
+ health.x + ","
+ health.y + ","
+ block.transform.rotation.x + ","
+ block.transform.rotation.y + ","
+ block.transform.rotation.z
);
}
}
// Step 3 disable those GameObjects again that where not active before
foreach(var obj in objectWasNotActive)
{
obj.SetActive(false);
}

Now it's really hard to debug this without seeing the code, but try checking this first.
Check if write is being properly initialized without errors, sometimes it fails silently.
Are you sure that all of your objects with those tags have the component BlockControl on them ?
try updating your code to this just to check where exactly is it failing
if (block.tag == "WoodBlock" || block.tag == "WoodSteps" || block.tag == "WoodRamp" || block.tag == "GlasBlock" || block.tag == "WoodDoor")
{
Debug.Log(block.name);
Debug.Log(block.GetComponent<BlockControl>());
Debug.Log(block.GetComponent<BlockControl>().GetHealth());
// SAVE
write.WriteLine(block.tag + "," + block.transform.position.x + "," + block.transform.position.y + "," + block.transform.position.z + "," + block.GetComponent<BlockControl>().GetHealth().x + "," + block.GetComponent<BlockControl>().GetHealth().y + "," + block.transform.rotation.x + "," + block.transform.rotation.y + "," + block.transform.rotation.z);
}
This way you can find out which block is causing the issue and if the problem is with the component or the GetHealth() function.

Related

How do i working with Arrays in a While loop?

I have tried to wipe this data while trying to export a database into my program.
The basic problem is that I do not know why he can not use LIKE in my SQL statement.
So I wanted to catch all DataRows and write them into an array, which I can edit later.
The program throws an exception:
Error message: System.IndexOutOfRangeException: "The index was outside the array area."
If I did something unusual or wrong in my Post I sincerely apologies, this is my first entry in this forum.
Code:
public void TestQuery()
{
string file = #"C:\Users\Michael\Downloads\7z1900-x64.msi";
// Get the type of the Windows Installer object
Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
// Create the Windows Installer object
WindowsInstaller.Installer installer = (WindowsInstaller.Installer)Activator.CreateInstance(installerType);
// Open the MSI database in the input file
Database database = installer.OpenDatabase(file, 0);
// Open a view on the Property table for the version property
View view = database.OpenView("SELECT * FROM `File`");
// Execute the view query
view.Execute(null);
// Get the record from the view
Record record = view.Fetch();
int i = 1;
string[] sreturns = new string[60];
while (record != null)
{
Console.WriteLine("Ausgabe: " + record.get_StringData(0) + '=' + record.get_StringData(1) + '=' + record.get_StringData(2) + '=' + record.get_StringData(3));
record = view.Fetch();
sreturns[i] = record.get_StringData(0).ToString();
i++;
}
}
First thing I see is that you're starting at 1, while (C#) arrays are 0-based.
In you screenshot I see that i is 60, so that would be the problem. Index 60 doesn't actually exist in your array, as it goes from 0 to 59.
You can add i < sreturns.Length to make sure you are in the array range.
Also, make sure you start with i = 0 and not 1.
int i = 0;
string[] sreturns = new string[60];
while (record != null && i < sreturns.Length)
{
Console.WriteLine("Ausgabe: " + record.get_StringData(0) + '=' + record.get_StringData(1) + '=' + record.get_StringData(2) + '=' + record.get_StringData(3));
record = view.Fetch();
sreturns[i] = record.get_StringData(0).ToString();
i++;
}
Why not using a list instead of an array?
List<string> sreturns = new List<string>();
while (record != null)
{
try
{
Console.WriteLine("Ausgabe: " + record.get_StringData(0) + '=' + record.get_StringData(1) + '=' + record.get_StringData(2) + '=' + record.get_StringData(3));
record = view.Fetch();
var result = record.get_StringData(0);
sreturns.Add(result.ToString());
}
catch (Exception e)
{
Console.WriteLine("No record...");
}
}
This way you dont need to worry about the array size - its maintainable -efficient - and if in the future the size change you don't have to worry about it.
List documentation here
What is the query with LIKE that you have tried? The following should work:
SELECT * FROM File WHERE FileName LIKE '%.exe' OR FileName LIKE '%.msi'
EDIT: On further investigation (https://learn.microsoft.com/en-us/windows/win32/msi/sql-syntax), the documentation seems to imply that the LIKE operator is not supported. But you could start off with an IS NOT NULL and do more complex filtering in the loop, like you're doing.
EDIT 2, expanding on Alex Leo's answer.
List<string> sreturns = new List<string>();
while (record != null)
{
Console.WriteLine("Ausgabe: " + record.get_StringData(0) + '=' + record.get_StringData(1) + '=' + record.get_StringData(2) + '=' + record.get_StringData(3));
var result = record.get_StringData(0);
if(!string.IsNullOrWhiteSpace(result) && (result.EndsWith(".exe") || result.EndsWith(".msi")))
{
sreturns.Add(result.ToString());
}
record = view.Fetch();
}
Note that the view.Fetch() inside the while loop has been moved to the end, or you would skip the first record, as well as get another null reference when the last record has already been read, but the while loop executes one more time.

Permissions on a folder

I have been looking for some time now and have not been able to find this. How can I set my program up to write or update a file from multiple users but only one group is allowed to open the read what is in the folder?
class Log_File
{
string LogFileDirectory = #"\\server\boiseit$\TechDocs\Headset Tracker\Weekly Charges\Log\Log Files";
string PathToXMLFile = #"\\server\boiseit$\scripts\Mikes Projects\Headset-tracker\config\Config.xml";
string AdditionToLogFile = #"\Who.Did.It_" + DateTime.Now.Month + "-" + DateTime.Now.Day + "-" + DateTime.Now.Year + ".txt";
XML XMLFile = new XML();
public void ConfigCheck()
{
if (!File.Exists(PathToXMLFile))
{
XMLFile.writeToXML(PathToXMLFile, LogFileDirectory + AdditionToLogFile);
}
}
public void CreateLogFile()
{
if (Directory.GetFiles(LogFileDirectory).Count() == 0)
{
XMLFile.writeToXML(PathToXMLFile, LogFileDirectory + AdditionToLogFile);
CreateFileOrAppend("");
}
else if (!File.Exists(XMLFile.readingXML(PathToXMLFile)))
{
XMLFile.writeToXML(PathToXMLFile, LogFileDirectory + AdditionToLogFile);
CreateFileOrAppend("");
}
else
{
FileInfo dateOfLastLogFile = new FileInfo(XMLFile.readingXML(PathToXMLFile));
DateTime dateOfCreation = dateOfLastLogFile.CreationTime;
if (dateOfLastLogFile.CreationTime <= DateTime.Now.AddMonths(-1))
{
XMLFile.writeToXML(PathToXMLFile, LogFileDirectory + AdditionToLogFile);
CreateFileOrAppend("");
}
}
}
public void CreateFileOrAppend(string whoDidIt)
{
using (IsolatedStorageFile storage = IsolatedStorageFile.GetStore((IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly | IsolatedStorageScope.User), null, null))
{
using (StreamWriter myWriter = new StreamWriter(XMLFile.readingXML(PathToXMLFile), true))
{
if (whoDidIt == "")
{
}
else
{
myWriter.WriteLine(whoDidIt);
}
}
}
}
This is my path where it needs to go. I have the special permission to open and write to the folder but my co workers do not. I am not allow to let them have this permission.
If I where to set up a database how would i change this code
LoggedFile.CreateFileOrAppend(Environment.UserName.ToUpper() + "-" + Environment.NewLine + "Replacement Headset To: " + AgentName + Environment.NewLine + "Old Headset Number: " + myDatabase.oldNumber + Environment.NewLine + "New Headset Number: " + HSNumber + Environment.NewLine + "Date: " + DateTime.Now.ToShortDateString() + Environment.NewLine);
I need it to pull current user, the agents name that is being affected the old headset and the new headset, and the time it took place.
While you create file, you have to set access rules to achieve your requirements. .
File.SetAccessControl(string,FileSecurity)
The below link has example
https://msdn.microsoft.com/en-us/library/system.io.file.setaccesscontrol(v=vs.110).aspx
Also the "FileSecurity" class object, which is an input parameter, has functions to set access rules, including group level control.
Below link has example
https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesecurity(v=vs.110).aspx
This question will be opened under a new question since I am going to take a different route for recording the data I need Thank you all for the help

Can Anyone help me retrieve data from parse using unity please?

I have managed to store data, but I can't retrieve it and i would be so grateful if someone could just help me get at least 1 example working.
First I am storing data when the user signs up:
public void SetupNewParseMember(ParseUser user)
{
ParseObject gameScore = new ParseObject("GameScore");
gameScore["cash"] = 500;
gameScore["playerName"] = user.Username;
gameScore["HighestCash"] = 500;
gameScore["GamesPlayed"] = 0;
Task saveTask = gameScore.SaveAsync();
}
This works fine, I can see the data in parse and all seems ok..
The problem is when i try to retrieve the objects.
public void SetupMainScreen(ParseUser user)
{
var query = ParseObject.GetQuery("GameScore").WhereEqualTo("playerName", user.Username);
query.FindAsync().ContinueWith(t =>
{
IEnumerable<ParseObject> results = t.Result;
List<ParseObject> resultsList = results.ToList();
DealWithResults(resultsList, user);
});
}
public void DealWithResults(List<ParseObject> resultsList, ParseUser me)
{
userGamesPlayed = resultsList[1].Get<int>("GamesPlayed");
userHighestCash = resultsList[2].Get<int>("HighestCash");
userCash = resultsList[3].Get<int>("Cash");
WelcomeText.text = "Welcome, " + me.Username + "\n" +
"Cash: $" + userCash + "\n" +
"Highest Cash: $" + userHighestCash + "\n" +
"Games Played: " + userGamesPlayed;
}
First I tried just making changes to the unity ui from inside the Query but that did not work, So i made an outside function and passed the results to it that way, and that still does not work?
I tried to debug what i was getting in the list with this:
foreach (var res in resultsList)
{
Debug.Log("Class Name = " + res.ClassName + "| Keys are: " + res.Keys);
}
But all it returned was:
Class Name = GameScore| Keys are: System.Collections.Generic.Dictionary`2+KeyCollection[System.String,System.Object]
Can anyone offer any insights?
EDIT2:
ok so first i found results list and its contents
http://i.imgur.com/IKcBbey.png
Then if i open it, it seems to be null ref?
http://i.imgur.com/VmSpi9c.png
But if i go digging, i found the info i need all the way down here
http://i.imgur.com/1Wwu5uc.png
Now just need to work out how to get it?
As there is only one set of data it is always accessible through resultsList[0]. What you want is:
double cash = (double)resultsList[0]["cash"];
string playerName = (string)resultsList[0]["playerName"];
double highestCash = (double)resultsList[0]["HighestCash"];
int gamesPlayed = (int)resultsList[0]["GamesPlayed"];
Though you probably want to check that resultsList is not null and contains one element before you try to dereference it.
Also as your ParseObject appears to be a Dictionary you might find this MSDN page useful.
Ended up solving it.. Much different to the examples...
I had to make a coroutine that called a function on callback to access the variables outside of the query.
I called it with
StartCoroutine(SetupMainScreen(me, DealWithResults));
then called this.
public IEnumerator SetupMainScreen(ParseUser user, Action<GameScore> callback)
{
var query = ParseObject.GetQuery("GameScore").WhereEqualTo("playerName", user.Username).FirstOrDefaultAsync();
while (!query.IsCompleted)
{
yield return null;
}
if (query.IsFaulted || query.IsCanceled)
{
Debug.Log("Getting of GameScores faulted or cancelled...");
}
else
{
var obj = query.Result;
if (obj != null)
callback(new GameScore(obj.Get<int>("cash"),obj.Get<string>("playerName"),obj.Get<int>("HighestCash"),obj.Get<int>("GamesPlayed")));
}
}
public void DealWithResults(GameScore gs)
{
WelcomeText.text = "Welcome, " + gs.Username + "\n" +
"Cash: $" + gs.Cash + "\n" +
"Highest Cash: $" + gs.HighestCash + "\n" +
"Games Played: " + gs.GamesPlayed;
}
And i just made a class to hold the objects.. Hopefully this helps someone else.

String list remove

I have this code:
List<string> lineList = new List<string>();
foreach (var line in theFinalList)
{
if (line.PartDescription != "")
lineList.Add(line.PartDescription + " " + line.PartNumber + "\n");
else
lineList.Add("N/A " + line.PartNumber + "\n");
//
//This is what I am trying to fix:
if (lineList.Contains("FID") || lineList.Contains("EXCLUDE"))
// REMOVE THE item in the lineList
}
I am trying to go through theFinalList in a foreach loop and add each line to a new list called lineList.
Once added, I want to remove any entries from that list that contain the text "FID" or "EXCLUDE".
I am having trouble removing the entry, can someone help me?
why add them when you want to remove them right after:
lineList = theFinalList.Select( line =>
{
if (line.PartDescription != "")
return line.PartDescription + " " + line.PartNumber + "\n";
else
return "N/A " + line.PartNumber + "\n";
})
.Where(x => !(x.Contains("FID") || x.Contains("EXCLUDE")))
.ToList();
The following code sample iterates through the lineList and removes lines that contain FID or EXCLUDE.
for(int i = lineList.Count - 1; i >= 0; i--)
{
if (lineList[i].Contains("FID") || lineList[i].Contains("EXCLUDE"))
lineList.RemoveAt(i);
}
It is important to traverse a list in reverse order when deleting items.
You can't remove the items in your theFinalList list while you are iterating over theFinalList in a foreach loop. In this case, you may get System.InvalidOperationException with the message “Collection was modified; enumeration operation may not execute.”
you have to do something like this:
List<string> removals = new List<string>();
foreach (string s in theFinalList)
{
//do stuff with (s);
removals.Add(s);
}
foreach (string s in removals)
{
theFinalList.Remove(s);
}
try
foreach (var line in theFinalList)
{
string T = "";
if (line.PartDescription != "")
T = line.PartDescription + " " + line.PartNumber + "\n";
else
T = "N/A " + line.PartNumber + "\n";
if (!(T.Contains("FID") || T.Contains("EXCLUDE"))
lineList.Add (T);
}
I think its more logical approach
Regex exclude = new Regex("FID|EXCLUDE");
foreach (var line in theFinalList.Where(
ln => !exclude.Match(ln.PartDescription).Success &&
!exclude.Match(ln.PartNumber ).Success))){
string partDescription = "N/A";
if(!string.IsNullOrWhiteSpace(line.PartDescription)){
partDescription = line.PartDescription;
}
lineList.Add(partDescription + " " + line.PartNumber + "\n");
}
edit regex for your needs (ignore case maybe or multiline, probably compiled too) and feel free to replace "\n" with Environment.NewLine
Try this:
var excludingTexts = new [] { "FID", "EXCLUDE" }
lineList = lineList.Where(y => !excludingTexts.Any(x => line.PartDescription.Contains(x) || line.PartNumber.Contains(x))).ToList();
Or you can rewrite it as:
var excludingTexts = new [] { "FID", "EXCLUDE" }
List<string> lineList = (from line in theFinalList
where !excludingTexts.Any(x => line.PartDescription.Contains(x) || line.PartNumber.Contains(x))
select line.PartDescription != "" ?
line.PartDescription + " " + line.PartNumber + "\n" :
"N/A " + line.PartNumber + "\n"
).ToList();

C# - Problem with reading XML more that once. Also error check if exists. [XElement]

Updated with the changes suggested below.
I've been trying to de-bug this for about 2 days now for 3-4 hours on and off and haven't gotten far. Any help is appreciated.
Alright, so essentially this runs every time someone dies. If I run it once, it works fine. However, If I run it a second time it returns nothing.
public static string Death(string username)
{
if (File.Exists(path))
{
XElement kd = XElement.Load(path);
Console.WriteLine("[SimplePlugin] Death Recorded for: " + username + "!");
var player = kd.Elements("player").Single(p => p.Attribute("name").Value == username);
int add = int.Parse(player.Element("deaths").Value) + 1; //Dies Here.
player.SetElementValue("deaths", add);
player.Save(path);
kills = int.Parse(player.Element("kills").Value);
deaths = int.Parse(player.Element("deaths").Value);
return (username + " has " + kills + " kills and " + deaths + " deaths.");
}
else
{
Console.WriteLine("[SimpleStats] Writing SimpleConfig.XML...");
XElement kd = new XElement(
"SimpleStats",
new XElement("player",
new XAttribute("name", username),
new XElement("kills", 0),
new XElement("deaths", 0),
new XElement("time", 0)));
kd.Save(path);
Console.WriteLine("[Simple] Done!");
return (username + " has " + kills + " kills and " + deaths + " deaths.");
}
}
My second problem comes when the player name="NAME" in the XML already exists. Even if the username matches, it re-creates it.
Code for this:
public static string Load(string username)
{
if (File.Exists(path))
{
XElement kd = XElement.Load(path);
var player = kd.Elements("player").SingleOrDefault(p => p.Attribute("name").Value == username);
Console.WriteLine(player);
if (player == null)
{
Console.WriteLine("[SimplePlugin] Writing new XML data for " + username + ".");
kd.Add(new XElement("player",
new XAttribute("name", username),
new XElement("kills", 0),
new XElement("deaths", 0),
new XElement("time", 0)));
kd.Save(path);
return (username + " has " + kills + " kills and " + deaths + " deaths.");
}
else
{
kills = int.Parse(player.Element("kills").Value);
deaths = int.Parse(player.Element("deaths").Value);
return (username + " has " + kills + " kills and " + deaths + " deaths.");
}
}
else
{
Console.WriteLine("Doesnt Exist");
Console.WriteLine("[SimpleStats] Writing SimpleConfig.XML...");
XElement player = new XElement(
"SimpleStats",
new XElement("player",
new XAttribute("name", username),
new XElement("kills", 0),
new XElement("deaths", 0),
new XElement("time", 0)));
player.Save(path);
Console.WriteLine("[Simple] Done!");
return (username + " has " + kills + " kills and " + deaths + " deaths.");
}
}
A string is sent to Load(). Load check to see if the xml exists. If not, creates it. If so, creates it. Then check to see if the username exists in the xml data. If it does it reads it and returns that string. If it doesn't, it creates it.
Also: "This question does not show any research effort; it is unclear or not useful"
How?
The second problem: you need to use Value property to get the attribute value.
var player = kd.Elements("player").SingleOrDefault(p => p.Attribute("name").Value == username);
You need to revise your entire code. For example, these lines will give you unexpected results:
kills = (int)player.Element("kills"); // explicit casting XElement to int, will return the pointer value.
deaths = (int)player.Element("deaths");
The correct sequence is
kills = int.Parse(player.Element("kills").Value);
deaths = int.Parse(player.Element("deaths").Value);
For your first issue, do this
player.Element("deaths").Value = Convert.ToInt32(player.Element("deaths").Value) + 1;
As XElement.Value is of type System.String

Categories

Resources