I'm currently following a brackeys tutorial on dialogue, however I want to import multiple names, as well as multiple lines of dialogue along with them, through a text file rather than handwriting them. This differs greatly from the tutorial, so I'm stuck on creating a Dialogue class. Here's what I wrote up:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Dialogue {
// text file input
public TextAsset textFile;
// list containing each line of the text file
protected string[] textLines;
// lists containing names and dialogues (written in the file like so: Bob;I like pancakes)
protected string[] names;
protected string[] dialogues;
// splits up the text file into the list of lines
textLines = textFile.text.Split('\n');
// iterates over the course of the list of lines, splitting up the names and dialogue, then sorting each into its respective list
for (int i = 0; i <= textLines.Length - 1; i++) {
names.Add(textLines[i].Split(';')[0]);
dialogue.Add(textLines[i].Split(';')[1]);
}
}
Unity, however, does not like this, and spits out loads of errors such as:
Assets\scripts\Dialogue.cs(12,15): error CS1519: Invalid token '=' in class, struct, or interface member declaration
and
Assets\scripts\Dialogue.cs(12,36): error CS1519: Invalid token '(' in class, struct, or interface member declaration
among others.
I assume there are limits when working with unity classes such as this one, however I evidently don't know them, and after looking through the internet, I was unable to find them. Any help with this is appreciated!
NOTE: I have never used Unity before, so take this with a grain of salt.
You can only assign class fields when you declare them, and you can only place for loops inside functions and properties (get and set are technically functions). Though class fields ARE modified in the application's lifetime, so there is no need to worry.
Also, string[]s do not have the .Add() method in them. Either convert the array into a List<string>, do an expensive operation that pushes new elements into an array or define a fixed size for the array.
You can do this:
/**
* You can do one of these:
* protected List<string> names; (we will use this)
* protected string[] names = new string[SOME_SIZE];
* protected string[] names; (and push elements)
*/
void AssignTextLines() {
// splits up the text file into the list of lines
textLines = textFile.text.Split('\n');
// iterates over the course of the list of lines, splitting up the names and dialogue, then sorting each into its respective list
for (int i = 0; i < textLines.Length; /* you don't need -1! */ i++) {
names.Add(textLines[i].Split(';')[0]); // only for List<T>!!
dialogues.Add(textLines[i].Split(';')[1]);
}
}
or this:
protected string[] textLines = textFile.text.Split('\n');
void Something() {
// then do the thing above
for (int i = 0; i < textLines.Length; i++) {
names.Add(textLines[i].Split(';')[0]); // only for List<T>!!
dialogues.Add(textLines[i].Split(';')[1]);
}
}
You can also replace the for loop with a foreach loop.
foreach (string txt in textLines) {
names.Add(txt.Split(';')[0]); // only for List<T>!!
dialogues.Add(txt.Split(';')[1]);
}
The error in your code is that you have written these:
// splits up the text file into the list of lines
textLines = textFile.text.Split('\n');
// iterates over the course of the list of lines, splitting up the names and dialogue, then sorting each into its respective list
for (int i = 0; i <= textLines.Length - 1; i++) {
names.Add(textLines[i].Split(';')[0]);
dialogue.Add(textLines[i].Split(';')[1]);
}
Inside the class instead of a method inside the class.
Coming to the actual problem, I think you should use a ScriptableObject in this case. They provide an Awake (Similar to Awake in MonoBehavior) method in which you can write the above code so that it gets executed when the game starts.
[CreateAssetMenu(fileName="Dialogue Asset", menuName="My Game/Dialogue Asset")]
public class Dialogue: ScriptableObject {
// text file input
public TextAsset textFile;
// list containing each line of the text file
protected string[] textLines;
// lists containing names and dialogues (written in the file like so: Bob;I like pancakes)
protected string[] names;
protected string[] dialogues;
void Awake() {
textLines = textFile.text.Split('\n');
for (int i = 0; i <= textLines.Length - 1; i++) {
names.Add(textLines[i].Split(';')[0]);
dialogue.Add(textLines[i].Split(';')[1]);
}
}
}
Now you can access the textLines and other things when the game is running. To create a new dialogue asset, you can go to Asset/My Game/Dialogue Asset from the menu bar in Unity and Edit them in the inspector. This is just a brief overview of ScriptableObject class. I suggest you watch Scriptable Objects - Brackey's to get the concept of scriptable objects better.
Related
I apologize if the question is very noobish, I have been only learning for 3 weeks. It's kind of overwhelming, even if you do simple things like I do.
So I'm trying to create a program that manages "accounts" as practice and puts the users and the corresponding passwords in two separate .txt files. Each line translates into a user or password accordingly. I use both lists and arrays, because arrays are easier to use in certain situations, but lists can have varying length.
I ran into a problem when I was creating a function for the program that changes the "administrator", the first user in the list, to a user, whose ID is given in form of user input. I read about how to do this, but I seem to have failed, as the intended swap does absolutely nothing. Users are returned to the list in the original order. Any further observation of why my code is messed up is also welcome.
Here's the actual code to this function of the program:
else if (input.Contains("changeadmin"))
{
foreach (string y in idKeysLocArray) //checks every line for username
{
if (input.Contains(y))
{
string[] idKeysLocArray2 = File.ReadAllLines(idKeysLoc); // creates new arrays in case .txt was updated by different function before
string[] passKeysLocArray2 = File.ReadAllLines(passKeysLoc);
static void Swap(ref string a, ref string b) // create function to swap two members of an array
{
string temp = a;
a = b;
b = temp;
}
Swap(ref idKeysLocArray2[0], ref idKeysLocArray2[Array.IndexOf(idKeysLocArray2, y)]); //using function to swap array members
Swap(ref passKeysLocArray2[0], ref passKeysLocArray2[Array.IndexOf(idKeysLocArray2, y)]);
var idKeysLocArray2List = idKeysLocArray2.ToList();
var passKeysLocArray2List = passKeysLocArray2.ToList();
foreach (string h in idKeysLocArray2List) // to check success
{
Console.WriteLine(h);
}
TextWriter w7 = new StreamWriter(idKeysLoc); // write swapped values to .txt file
foreach (string s in idKeysLocArray2List)
w7.WriteLine(s);
w7.Close();
TextWriter w8 = new StreamWriter(passKeysLoc);
foreach (string s in passKeysLocArray2List)
w8.WriteLine(s);
w8.Close();
}
}
}
static string readfileName(string[] name)
{
using (StreamReader file = new StreamReader("StudentMarks.txt"))
{
int counter = 0;
string ln;
while ((ln = file.ReadLine()) != null)
{
if (ln.Length > 4)
{
name[counter] = ln;
counter++;
}
}
file.Close();
return name;
}
}
This is the procedure I'm currently trying to return the array name[50] but the compile time error I can't fix states
"Error CS0029 Cannot implicitly convert type 'string[]' to 'string' "
You don't need to. Your main method passed the array to this method, this method filled it. It doesn't need to hand it back because the object pointed to by your 'name` variable is the same object as pointed to by the original variable in the main method; your main method already has all the array data:
static void Main(){
var x = new string[10];
MyMethod(x);
Console.Write(x[0]); //prints "Hello"
}
static void MyMethod(string[] y){
y[0] = "Hello";
}
In this demo code above we start out with an array of size 10 that is referred to by a variable x. In memory it looks like:
x --refers to--> arraydata
When you call MyMethod and pass x in, c# will create another reference y that points to the same data:
x --refers to--> arraydata <--refers to-- y
Now because both references point to the same area of memory anything that you do with y, will also affect what x sees. You put a string (like I did with Hello) in slot 0, both x and y see it. When MyMethod finishes, the reference y is thrown away, but x survives and sees all the changes you made when working with y
The only thing you can't do is point y itself to another different array object somewhere else in memory. That won't change x. You can't do this:
static void MyMethod(string[] y){
y = new string[20];
}
If you do this your useful reference of x and y pointing to the same area of memory:
x ---> array10 <--- y
Will change to:
x ---> array10 y ---> array20
And then the whole array20 and the y reference will be thrown away when MyMethod finishes.
The same rule applies if you call a method that supplies you an array:
static void MyMethod(string[] y){
y = File.ReadAllLines("some path"); //this also points y away to a new array made by ReadAllLines
}
It doesn't matter how or who makes the new array. Just remember that you can fiddle with the contents of an object pointed to by y all you like and the changes will be seen by x, but you can't change out the entire object pointed to by y and hope x will see it
in that case you WOULD have to pass it back when you're done:
static string[] MyMethod(string[] y){
y = new ...
return y;
}
And the main method would have to capture the change:
Main(...){
string[] x = new string[10];
string[] result = MyMethod(x);
}
Now, while I'm giving this mini lesson of "pass by reference" and "pass by value" (which should have been called "pass by original reference" and "pass by copy of reference") it would be useful to note that there is a way to change things so MyMethod can swap y out for a whole new object and x will see the change too.
We don't really use it, ever; there is rarely any need to. Just about the only time it's used is in things like int.Parse. I'm telling you for completeness if education so that if you encounter it you understand it but you should always prefer a "change the contents but not the whole object" or a "if you make a new object pass it back" approach
By marking the y argument with the ref keyword, c# wont make a copy of the reference when calling the method, it will use the original reference and temporarily allow you to call it y:
static void MyMethod(ref string[] y){
y = new array[20];
}
Our diagram:
x ---> array10data
Temporarily becomes:
x a.k.a y ---> array10data
So if you point y to a new array, x experiences the change too, because they're the same reference; y is no longer a different reference to the same data
x a.k.a y ---> array20data
Like I say, don't use it- we always seek to avoid it for various reasons.
Now, I said at the start "you don't need to" - by that, and for the reasons above, I meant you don't need to return anything from this method
Your method receives the array it shall fill (from the file) as a parameter; it doesn't make a new array anywhere so there isn't any need to return the array when done. It will just put any line longer than 4 chars into an array slot. It could then finish without returning anything and the method that called this method will see the changes it made in the array. This is just like my code, where MyMethod changes slot 0 of the array, MyMethod was declared as void so it didn't need to make a return statement , and my Main method god could still see the Hello that I put in the array. In the same vein, your Main method will see all those lines from the file if you make your ReadFileName method (which should perhaps be called FillArray) because it fills the array called name
The most useful thing your method could return is actually an integer saying how many lines were read; the array passed in is of a fixed size. You can't resize it because that entails making a new array which won't work for all those reasons I talked about above. If you were to make a new array and return it there wouldn't be any point in passing an array in.
There are thus several ways we could improve this code but to my mind they come down to two:
don't pass an array in; let this method make a new array and return it. The new array passed back can be exactly sized to fit
keep with the "pass an array in" idea and return an integer of how many lines were actually read instead
For the second idea (which is the simplest to implement) you have to change the return type to int:
static int ReadFileName(string[] name)
And you have to return that variable you use to track which slot to put the next thing in, counter. Counter is always 1 greater than the number of things you've stored so:
return counter - 1;
Your calling method can now look like:
string[] fileData = new string[10000]; //needs to be big enough to hold the whole file!
int numberOfLinesRead = ReadFileName(fileData);
Can you see now why ReadFileName is a bad name for the method? Calling it FillArrayFromFile would be better. This last line of code doesn't read like a book, it doesn't make sense from a natural language perspective. Why would something that looks like it reads a file name (if that even makes sense) take an array and return an int - calling it ReadFileName makes it sound more like it searches an array for a filename and returns the slot number it was found in. Here ends the "name your methods appropriately 101"
So the other idea was to have the Read method make its own array and return it. While we are at it, let's call it ReadFileNamed, and have it take a file path in so it's not hard coded to reading just that one file. And we will have it return an array
static string[] ReadFileNamed(string filepath)
^^^^^^^^ ^^^^^^^^^^^^^^^
the return type the argument passed in
Make it so the first thing it does is declare an array big enough to hold the file (there are still problems with this idea, but this is programming 101; I'll let them go. Can't fix everything using stuff you haven't been taught yet)
Put this somewhere sensible:
string lines = new string[10000];
And change all your occurrences of "name" to be "lines" instead - again we name our variables we'll just like we name our methods sensibly
Change the line that reads the fixed filename to use the variable name we pass in..
using (StreamReader file = new StreamReader(filepath))
At the end of the method, the only thing left to do is size the array accurately before we return it. For a 49 line file, counter will be 50 so let's make an array that is 49 big and then fill it using a loop (I doubt you've been shown Array.Copy)
string[] toReturn = new string[counter-1];
for(int x = 0; x < toReturn.Length; x++)
toReturn[x] = lines[x];
return toReturn;
And now call it like this:
string[] fileLines = ReadFileNamed("student marks.txt");
If you're looking to return name[50] and you know that will be populated, why not go with:
static string readfileName(string[] name)
{
using (StreamReader file = new StreamReader("StudentMarks.txt"))
{
int counter = 0;
string ln;
while ((ln = file.ReadLine()) != null)
{
if (ln.Length > 4)
{
name[counter] = ln;
counter++;
}
}
file.Close();
return name[50];
}
}
You're getting the error because your method signature indicates that you're going to return a string, but you're defining name as a string[] in the argument. If you simply select a single index of your array in the return statement, you'll only return a string.
You have defined your method to return a string, yet the code inside is returning name, which is a string[]. If you want it to return a string[], then change the signature to specify that:
static string[] ReadFileName(string[] name)
However, since your method is only populating the array that was passed in, it's not really necessary to return the array, since the caller already has a reference to the array we're modifying (they passed it to our method in the first place).
There is a potential problem here, though
We're expecting the caller to pass us an array of the appropriate length to hold all the valid lines from the file, yet that number is unknown until we read the file. We could return an array of the size they specified with either empty indexes at the end if it was too big, or incomplete data if it was too small, but instead we should probably just return them a new array, and not require them to pass one to us.
Note that it's easier to use a List<string> instead of a string[], since lists don't require any knowledge of their size at instantiation (they can grow dynamically). Also, we no longer need a counter variable (since we're using the Add method of the list to add new items), and we can remove the file.Close() call since the using block will call that automatically (one of the cool things about them):
static string[] ReadFileName()
{
List<string> validLines = new List<string>();
using (StreamReader file = new StreamReader("StudentMarks.txt"))
{
string ln;
while ((ln = file.ReadLine()) != null)
{
if (ln.Length > 4)
{
validLines.Add(ln);
}
}
}
return validLines.ToArray();
}
And we can simplify the code even more if we use some static methods of the System.IO.File class:
static string[] ReadFileName()
{
return File.ReadLines("StudentMarks.txt").Where(line => line.Length > 4).ToArray();
}
We could also make the method a little more robust by allowing the caller to specify the file name as well as the minimum line length requirement:
static string[] ReadFileName(string fileName, int minLineLength)
{
return File.ReadLines(fileName)
.Where(line => line.Length >= minLineLength).ToArray();
}
Well, you are trying to do several thing in one method:
Read "StudentMarks.txt" file
Put top lines into name existing array (what if you have too few lines in the file?)
return 50th (magic number!) item
If you insist on such implementation:
using System.Linq;
...
static string readfileName(string[] name)
{
var data = File
.ReadLines("StudentMarks.txt")
.Where(line => line.Length > 4)
.Take(name.Length);
int counter = 0;
foreach (item in data)
if (counter < name.Length)
name[counter++] = item;
return name.Length > 50 ? name[50] : "";
}
However, I suggest doing all things separately:
// Reading file lines, materialize them into string[] name
string[] name = File
.ReadLines("StudentMarks.txt")
.Where(line => line.Length > 4)
// .Take(51) // uncomment, if you want at most 51 items
.ToArray();
...
// 50th item of string[] name if any
string item50 = name.Length > 50 ? name[50] : "";
Edit: Splitting single record (name and score) into different collections (name[] and score[]?) often is a bad idea;
the criterium itself (line.Length > 4) is dubious as well (what if we have Lee - 3 letter name - with 187 score?).
Let's implement Finite State Machine with 2 states (when we read name or score) and read (name, score) pairs:
var data = File
.ReadLines("StudentMarks.txt")
.Select(line => line.Trim())
.Where(line => !string.IsNullOrEmpty(line));
List<(string name, int score)> namesAndScores = new List<(string name, int score)>();
string currentName = null;
foreach (string item in data) {
if (null == currentName)
currentName = item;
else {
namesAndScores.Add((currentName, int.Parse(item)));
currentName = null;
}
}
Now it's easy to deal with namesAndScores:
// 25th student and his/her score:
if (namesAndScores.Count > 25)
Console.Write($"{namesAndScores[25].name} achieve {namesAndScores[25].score}");
We are using a object from the asset store that makes usage's of a mesh renderer with 32 materials in it. We need to implement functionality that makes it possible to replace 1 single material in the list, and keep the rest.
I have tried several things, but I keep struggeling with the materials list, that is why I decided to ask you guys for help.
public Material TestMaterial;
void Update()
{
for (int i = 0; i < meshRenderer.sharedMaterials.Length; i++)
{
if (meshRenderer.sharedMaterials[i].name == "replaceableMat")
{
// Replace with TestMaterial
}
}
}
The above code is how I kinda want to use it.
The TestMaterial object is not null, it is selected from the Unity Editor, so that is fine.
Could someone give me some insight?
Thanks in forward!
from Renderer.sharedMaterials:
Note that like all arrays returned by Unity, this returns a copy of materials array. If you want to change some materials in it, get the value, change an entry and set materials back.
You have to do exactly that. Store the materials in a temporary variable, change entries and write the entire array back to sharedMaterials when done:
var materials = meshRenderer.sharedMaterials;
for(int i = 0; i < materials.Length; i++)
{
if(!string.Equals(materials[i].name, "replaceableMat") continue;
materials[i] = TestMaterial;
}
meshRenderer.sharedMaterials = materials;
The reason in the background is that sharedMaterials is not a field but a property.
Since the assignment of sharedMaterial[i] is not an assignment to the whole property what it does is just replacing that entry in a temporary array but not actually assigning it back to the Renderer component.
Only by assigning a value to the entire property actually makes the property execute it's setter and writes the array back to the Renderer component.
This should work.
void Update()
{
for (int i = 0; i < meshRenderer.sharedMaterials.Length; i++)
{
if (meshRenderer.sharedMaterials[i].name == "replaceableMat")
{
// Replace with TestMaterial
renderer.sharedMaterials[i] = TestMaterial
}
}
}
I was working on a small program that basically reads from a txt multiple arrays and writes them to another file, but, additionally, it should generate a unique number and place it just before the information. I got the first part working with no problems but the second part is causing me problems even though it should work.
public static void Main(string[] args)
{
StreamReader vehiclereader = new StreamReader(#"C:\Users\Admin\Desktop\Program\vehicles.txt");
string line = vehiclereader.ReadToEnd();
string ID;
string category;
string Type;
string Brand;
string Model;
string Year;
string Colour;
while (line != null)
{
var parts = line.Split(',');
Type = parts[0];
Brand = parts[1];
Model = parts[2];
Year = parts[3];
Colour = parts[4];
Console.WriteLine(line);
string[] lines = { line };
System.IO.File.WriteAllLines(#"C:\Users\Admin\Desktop\Program\vehicles2.txt", lines);
List<string> categories = new List<string>();
categories.Add(Type);
int count = categories.Where(x => x.Equals(Type)).Count();
ID = Type.Substring(0, 4) + count.ToString("00");
Console.ReadKey();
}
}
}
Currently, this code reads from a txt file, displays it into the console and writes it back to another txt file. This is fine, the part that is not willing to work is the unique number generator.
Last 5 lines of code are supposed to add a unique number just before 'Type' data and would always start from 001. If the 'Type' data is identical, then the number just grows in ascending order. Otherwise, the unique number should reset for new types and start counting from 001 and should keep growing for identical types. (e.g. For all lightweight vehicles the counter should be the same and for heavyweight vehicles the counter should be different but count all of the heavy vehicles)
I'm open to any help or suggestions!
There are a variety of issues and suggestions with this code, allow me to list them before providing a corrected version:
StreamReader is disposable, so put it in a "using" block.
ReadToEnd reads the entire file into a single string, whereas your code structure is expecting it to return a line at a time, so you want the "ReadLine" method.
The value of line does not get modified within your loop, so you will get an infinite loop (program that never ends).
(Suggestion) Use lower case letters at the start of your variable names, it will help you spot what things are variables and what are classes/methods.
(Suggestion) The local variables are declared in a wider scope than they are needed. There is no performance hit to only declaring them within the loop, and it makes your program easier to read.
"string[] lines = { line };" The naming implies that you think this will split the single line into an array of lines. But actually, it will just create an array with one item in it (which we've already established is the entire contents of the file).
"category" is an unused variable; but actually, you don't use Brand, Model, Year or Colour either.
It would have helped if the question had a couple of lines as an example of input and output.
Since, you're processing a line at a time, we might as well write the output file a line at a time, rather than hold the entire file in memory at once.
The ID is unused, and that code is after the line writing the output file, so there is no way it will appear in there.
"int count = categories.Where(x => x.Equals(type)).Count();" is inefficient, as it iterates through the list twice: prefer "int count = categories.Count(x => x.Equals(type));"
Removed the "Console.Write", since the output goes to a file.
Is that "Console.ReadKey" meant to be within the loop, or after it? I put it outside.
I created a class to be responsible for the counting, to demonstrate how it is possible to "separate concerns".
Clearly I don't have your files, so I don't know whether this will work.
class Program
{
public static void Main(string[] args)
{
var typeCounter = new TypeCounter();
using (StreamWriter vehicleWriter = new StreamWriter(#"C:\Users\Admin\Desktop\Program\vehicles2.txt"))
using (StreamReader vehicleReader = new StreamReader(#"C:\Users\Admin\Desktop\Program\vehicles.txt"))
{
string line;
while ((line = vehicleReader.ReadLine()) != null)
{
var parts = line.Split(',');
string type = parts[0].Substring(0, 4); // not sure why you're using substring, I'm just matching what you did
var identifier = typeCounter.GetIdentifier(type);
vehicleWriter.WriteLine($"{identifier},{line}");
}
}
Console.ReadKey();
}
}
public class TypeCounter
{
private IDictionary<string, int> _typeCount = new Dictionary<string, int>();
public string GetIdentifier(string type)
{
int number;
if (_typeCount.ContainsKey(type))
{
number = ++_typeCount[type];
}
else
{
number = 1;
_typeCount.Add(type, number);
}
return $"{type}{number:00}"; // feel free to use more zeros
}
}
I have to complete a project in C# to find number of methods per java class.
I could find all methods in the .java file using c# regular expression, but what I want is to find the number of methods per each and every class, including inner classes. Can any one help me.
string[] lines = File.ReadAllLines(file);
int countLine = 0;
int AllCount = 0;
foreach (string line in lines)
{
countLine = MethodsCount(line);
AllCount = AllCount + countLine;
}
label5.Text = AllCount.ToString();
Here's the method-counting method.
private int MethodsCount (string LineOperator)
{
int count = 0;
string[] words = LineOperator.Split('{');
foreach (string word in words)
{
if (Regex.IsMatch(word, #"(static\s|final\s)?(public|private|internal)(\sstatic|\sfinal)?\s(int|boolean|void|double|String|long|String\[\]|String\[\]\[\])?\s([a-z]|[A-Z]+[a-z]+|[a-z]+[A-Z]+)+(\s)*\((\s|\n|\r)*"))
{
count = count + 1;
}
}
return count;
}
if we consider a class
public class vehicle {
public method1() {
---------
}
public method2() {
------
}
public class car extends vehicle {
public method3() {
-------
}
}
}
I want to get the output there are this number of methods in vehicle class,this number of methods in car class like wise.
Parsing a Java source file with just regex is flaky at best. There are so many different ways to format or annotate the code that you'll easily miss valid method declarations. The regex in your code sample does not handle line breaks, generic methods, the throws clause, arbitrary return types, the synchronized modifier or annotated arguments, two method declarations on the same line...
You'd have to drop regex and build (or reuse) a full parser to be sure you get everything. Fortunately, if you have access to a JDK you can take a shortcut.
First, compile the Java source code. This will give you an output directory full of .class files (inner classes get their own file). Scan the output directory to collect all the class names you need to analyze.
Then, for each class, run the javap command, which will give you this kind of output:
barend#TURMINDER-XUSS /tmp$ javap java.util.Iterator
Compiled from "Iterator.java"
public interface java.util.Iterator{
public abstract boolean hasNext();
public abstract java.lang.Object next();
public abstract void remove();
}
This is much easier to parse than a full Java source file. You can just count all lines containing \s[a-zA-Z][a-zA-Z0-9_]*\( and you have your method count. Use the 'compiled from' information to get to the method count per source file.
(edit) NOTE: javap doesn't print private methods by default. pass the -private argument to include these in the output.