Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Is there a way to replace names in a class structure code? (idk the right terms to use here!)

Discussion in 'Scripting' started by infinitypbr, Mar 18, 2017.

  1. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Code (CSharp):
    1. for (int i = 0; i < master.data[d].floats.Count; i++){
    2.      master.data [d].items [itemID].floats.Add (new Floats (master.data [d].floats[i].name));
    3.         }
    I have the code above, and a similar bit of code for "ints/Ints", "bools/Bools" and more types.

    It's very annoying having to add another set of code for each type. I'd like to be able to have a class of "Types", with things like "floats", "Floats" etc, and then replace the code to be something like this....

    Code (CSharp):
    1. for (int t = 0; t < type.Count; t++){
    2.      for (int i = 0; i < master.data[d].{type[t].smallName}.Count; i++){
    3.           master.data [d].items [itemID].{type[t].smallName}.Add (new {type[t].bigName} (master.data [d].{type[t].smallName}[i].name));
    4.      }
    5. }
    Is there a way to do that?
    THANKS!!!
     
  2. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Sounds like a job for Generics! https://msdn.microsoft.com/en-us/library/512aeb7t.aspx?f=255&MSPPError=-2147217396

    Assuming the class with the .floats and .ints is called MasterData, you could do something like this:
    Code (csharp):
    1. public void DoTheTypes<Tsmall, Tbig>(Func<MasterData, List<Tsmall>> listGetter) {
    2.   for (int i = 0; i < listGetter(master.data[d]).Count; i++){
    3.       listGetter(master.data [d].items [itemID]).Add (new Tbig (listGetter(master.data [d])[i].name));
    4.   }
    5. }
    6.  
    7. public void DoAllTheTypes() {
    8.   DoTheTypes<int, Int>(m => m.ints);
    9.   DoTheTypes<float, Float>(m => m.floats);
    10. }
    11.  
    For added fun, you could create a list of the matching smallType, BigType, getter, and then loop through that in DoAllTheTypes instead of manually.

    PS: I love your monster packs! ;)
     
    infinitypbr likes this.
  3. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Thanks! I'm confused :) I had looked into Generics for this twice now, but both times it just didn't seem to make sense, because I had to declare the type before I wanted to...something like that. I was learning about it using the Learn video: https://unity3d.com/learn/tutorials/topics/scripting/generics

    For the first line, what does the "Func" part the "listgetter" part do?
     
  4. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    In this code, the GenericTest method should work (it probably has issues still) for the generic type T. However, I'm still unclear about how to replace the "smallName" ("dataFloats" in the example) with the passed string smallName.

    I'm guessing maybe the "listgetter" part from the example above is what I'm looking for, but i"m not really sure how.

    Code (CSharp):
    1.  
    2. public void GenericTest<T>(string smallName){
    3.         for (int a = 0; a < master.data.Count; a++) {
    4.             for (int t = 0; t < master.data [a].items.Count; t++) {
    5.                 for (int v = 0; v < master.data [a].items [t].dataFloats.Count; v++) {                
    6.                     Debug.Log ("dataName is " + master.data [a].items [t].dataFloats [v].dataName);
    7.                     DataFloats newTestList = new T (master.data [d].floats [i].name);
    8.                 }
    9.             }
    10.         }
    11.     }
    12.  
    13.     public void DoGenericTest(){
    14.         GenericTest<DataFloats> ("dataFloats");
    15.         GenericTest<Floats> ("floats");
    16.         GenericTest<Ints> ("ints");
    17.         GenericTest<GameObjects> ("gameObjects");
    18.     }
     
  5. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Yeah, generics can be complicated to figure out until you've used them for a while. Func<> is a way to pass a function as an argument to another function. It's like Action<> if you've seen that, except Func<> can have a return value and Action<> can't. Func<int, string> means a function that takes an int and returns a string. Like "public string SomeFunction(int num)". Func<MasterData, T> would be a function that takes a MasterData and returns T. T in my code gets inferred from the DoTheTypes() call in my example. Instead of passing the name of the field "dataFloats" you're passing a function that just returns that actual field. I don't know if MasterData is the name of the class, it should be whatever class master.data[a].items[t] is. This should make your code work:

    Code (csharp):
    1.  
    2. public void GenericTest<T>(Func<MasterData, T> getter){
    3.         for (int a = 0; a < master.data.Count; a++) {
    4.             for (int t = 0; t < master.data [a].items.Count; t++) {
    5.                 for (int v = 0; v < getter(master.data [a].items [t]).Count; v++) {                
    6.                     Debug.Log ("dataName is " + getter(master.data [a].items [t])[v].dataName);
    7.                     DataFloats newTestList = new T (master.data [d].floats [i].name); //note: here you call "floats" which is different than "dataFloats"? Is that a typo or on purpose?
    8.                 }
    9.             }
    10.         }
    11.     }
    12.  
    13.     public void DoGenericTest(){
    14.         GenericTest<DataFloats> (m => m.dataFloats);
    15.         GenericTest<Floats> (m => m.floats);
    16.         GenericTest<Ints> m => m.ints);
    17.         GenericTest<GameObjects> (m => m.gameObjects);
    18.     }
    If you haven't used lambdas before (the "m => m.something"), it is a way of creating a small temporary function; in this case "m => m.dataFloats" is basically a short form of this:
    Code (csharp):
    1. private DataFloats MyFunction(MasterData m) {
    2.   return m.dataFloats;
    3. }
    Lambdas: https://msdn.microsoft.com/en-us/library/bb397687.aspx
     
    infinitypbr likes this.
  6. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Thanks -- I haven't used Lambdas before (didn't even know what they were called!) and I'm new to the way of passing the function as in Func<>. I'll look into this and see if I can figure it out :D
     
  7. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Iv'e got to head out now and stop for the moment. Master is a class, as is Data.

    Items is also a class, and DataFloats, Floats, GameObjects etc are all classes. Each one has a string dataName.

    Code (CSharp):
    1. // GenericTest, the argument is passing Items and returning a Type?
    2.     public void GenericTest<T>(Func<Items, T> getter){
    3.         for (int a = 0; a < master.data.Count; a++) {
    4.             for (int i = 0; i < master.data [a].items.Count; i++) {
    5.                 // So "getter(master.data [a].items [t])" is passing items[t] and expecting a type as return?  Or...idk.
    6.                 for (int v = 0; v < getter(master.data [a].items [i]).Count; v++) {              
    7.                     Debug.Log ("dataName is " + getter(master.data [a].items [i])[v].dataName);
    8.  
    9.  
    10.  
    11.                     // DataFloats newTestList = new T (master.data [d].dataFloats [i].name);  // Was a typo I think
    12.                 }
    13.             }
    14.         }
    15.     }
    16.  
    17.     // If I'm reading this correctly, the Lambda expression is saying "If given i, return i.dataFloats", where i will be the Items class?
    18.     public void DoGenericTest(){
    19.         GenericTest<DataFloats> (i => i.dataFloats);
    20.         GenericTest<Floats> (i => i.floats);
    21.         GenericTest<Ints> (i => i.ints);
    22.         GenericTest<GameObjects> (i => i.gameObjects);
    23.     }
    I've changed the getter function to Items, T. Am I correct that it's supposed to return something of type T if given something of type Items? And it would be based on the lambda from the DoGenericTest function, so if given an object of type Items, it'll return itemsObjectGiven.dataFloats (which itself is a list of DataFloats)?

    It's slowly coming together in my brain.
     
  8. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    I'm at a loss, I think. I'm not sure how to go about this -- I'm trying right now to take it all back, simplify it so I can figure out how to work these new concepts and build from there, but I must be missing something.

    This is as close as I've gotten. I can see in the console correct information:

    Archer | v = 0 | System.Collections.Generic.List`1[DataFloats] | DataFloats

    It shows that getter(master.data [a].items [t]) is a List of dataName, and that getter(master.data [a].items [t])[v].dataName)[v] is DataFloats

    However, the commented out line will throw a console error, that T doesn't contain a definition for dataName.

    Code (CSharp):
    1.     public void GenericTest<T>(Func<Items, List<T>> getter){
    2.         for (int a = 0; a < master.data.Count; a++) {
    3.             for (int t = 0; t < master.data [a].items.Count; t++) {
    4.                 for (int v = 0; v < getter(master.data [a].items [t]).Count; v++) {    
    5.                     Debug.Log (master.data [a].items [t].name + " | v = " + v + " | " + getter (master.data [a].items [t]) + " | " + getter (master.data [a].items [t]) [v]);
    6.                     //Debug.Log ("dataName is " + getter(master.data [a].items [t])[v].dataName);
    7.                 }
    8.             }
    9.         }
    10.     }
    11.  
    12.     public void DoGenericTest(){
    13.         GenericTest<DataFloats> (m => m.dataFloats);
    14.         //GenericTest<Floats> (m => m.floats);
    15.         //GenericTest<Ints> (m => m.ints);
    16.         //GenericTest<GameObjects> (m => m.gameObjects);
    17.     }
    It seems that since T isn't a known type, the system doesn't think it have a definition for dataName. Is there any way to bypass this?
     
  9. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Huh -- I've stumbled upon a better way of googling this question, and then a potential solution. Javascript has an "Eval()" function, which will run code. Since it's an editor thing, it should be a good solution, I'd think. I need to test it, but if it works, then I should be able to do exactly what I want to do. I hope!
     
  10. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,738
    You can put constraints on T. This code will only allow GenericTest on a type which inherits from SomeBaseType, which has dataName. Since the compiler now knows that that is the case, it should allow you to use .dataName.
    Code (csharp):
    1. public class SomeBaseType {
    2. public string dataName;
    3. }
    4. public void GenericTest<T>(T thing) where T : SomeBaseType {
    5. Debug.Log(thing.dataName);
    6. }
     
    infinitypbr likes this.
  11. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Yeah, what StarManta said. If all of the things are subclasses of DataClass or whatever it's called, then you can do:

    Code (csharp):
    1. public void GenericTest<T>(Func<Items, List<T>> getter) where T : DataClass {
     
    infinitypbr likes this.
  12. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149

    That works, but only if I specify the class. @makeshiftwings you mention "Subclasses"--

    Master is a scriptable object.
    Data is a Class
    Items is a class
    DataFloats, DataInts, Floats, Ints, etc are all classes

    So none of them inherit from each other -- I think that's the right term? Is it better for them to inherit from each other, aka is that what you mean by subclass?
     
  13. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Yeah, if they all have a member called "dataName" then it would make sense for them to all inherit from the same base class. Or you could have them all implement the same interface, and have the interface include dataName.
     
    StarManta likes this.
  14. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    I don't think generics is the right way to approach this particular problem. If you had time to completely rework the design of your system, maybe creating a common interface would allow generics to work for you. However, it's likely you have constraints that preclude this option such as not owning the source code for all the types.

    To answer your original question directly, Reflection is the name of the technology you need in C#. I must warn you however, Reflection is usually a red flag that your design is not ideal for the task at hand. It can be confusing to read and write and almost always exhibits slower execution than an equivalent solution done with, say, interfaces or generics.

    In this case it seems you are working against the type system. In my experience this often happens when you are allowing end users to manipulate values using an editor that doesn't differentiate between types the same way c# does. If you explain more about your use case and design, someone may have ideas on how to avoid this issue in the future.
     
    infinitypbr likes this.
  15. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,738
    +1 to this. In addition, using Reflection is less reliable - a single typo can ruin things and give you runtime errors (as opposed to compile errors, which are much easier to detect and fix), and it doesn't run on all platforms (though you should be fine in editor code.)
     
    infinitypbr likes this.
  16. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    I'm basically building a system that lets me populate my game class structure. At first I just was building a class structure, but then I realized I could do better, make it more versatile, and now what was a "class" before is now what I'm calling a "Category". Instead of a Class for "Stats" and one for "Races" and one for "CharacterClasses" etc, I have a list of entries in a class called "Data".

    Data[0].name may equal "Stats", for instance.

    And Data has a list called "items" of the class Items. Data & Items both have Lists for various types of information -- a class called "Floats" just holds a string name and a float value. This way a data[0].items[0].floats[0].name = "Default Stat Value".

    idk if this is making sense. The List for Floats (And other data holding classes, including ones that hold a list of values instead of a single value), is set in the Data class. This way all Items will have the same set of data lists.

    For instance, the single float value "Default Stat Value", is something that all Items in the Stats (Data class) will require.

    I go a bit further though, and allow one Category to reference another. So a Data entry for "Classes" will include a reference to the Data entry "Stats", but in float value . That's the Class "DataFloats", which has a name, and "dataName" (name of the referenced category), and a List of float values. I may call this "Stat Proficiencies", and now when I edit each race, I can set their default stat proficiency -- a value I'll use later in the actual logic of the game.

    The benefit here is that I can add and delete Stats and they'll be automatically added to all other lists that reference the Stats data list.

    Does that make sense?

    The problem is that there are a lot of types -- floats, ints, bools, strings, Texture2ds, audioClips, gameObjects etc. For each one, I need a single class and a "Data" class. Like "Floats" and "DataFloats". (Although I think I may be able to combine those into one class...I may try to do that as I need to re-strcuture this anyway, to make it work with the advice given so far).

    Whenever I want to code any operation, such as AddItemToDataCategory, the method ends up being very long, as it needs redundant loops for each data type.

    Code (CSharp):
    1. public void AddItemToDataCategory(string name, int d){
    2.         // Check name first
    3.         for (int i = 0; i < master.data [d].items.Count; i++) {                                        // For each current item...
    4.             if (master.data [d].items[i].name == master.data[d].newItemName) {                        // if the names match
    5.                 // Leave an error in the console
    6.                 Debug.LogError("Error:  The name " + master.data[d].newItemName + " has already been assigned to an Item in this Category.");
    7.                 return;                                                                                // break the function
    8.             }
    9.         }
    10.         // Add the new item
    11.         master.data[d].items.Add (new Items (master.data[d].newItemName));
    12.         int itemID = master.data[d].items.Count - 1;
    13.         // Now add custom variables
    14.         for (int i = 0; i < master.data[d].floats.Count; i++){
    15.             master.data [d].items [itemID].floats.Add (new Floats (master.data [d].floats[i].name));
    16.         }
    17.         for (int i = 0; i < master.data[d].ints.Count; i++){
    18.             master.data [d].items [itemID].ints.Add (new Ints (master.data [d].ints[i].name));
    19.         }
    20.         for (int i = 0; i < master.data[d].strings.Count; i++){
    21.             master.data [d].items [itemID].strings.Add (new Strings (master.data [d].strings[i].name));
    22.         }
    23.         for (int i = 0; i < master.data[d].bools.Count; i++){
    24.             master.data [d].items [itemID].bools.Add (new Bools (master.data [d].bools[i].name));
    25.         }
    26.         for (int i = 0; i < master.data[d].gameObjects.Count; i++){
    27.             master.data [d].items [itemID].gameObjects.Add (new GameObjects (master.data [d].gameObjects[i].name));
    28.         }
    29.         for (int i = 0; i < master.data[d].texture2Ds.Count; i++){
    30.             master.data [d].items [itemID].texture2Ds.Add (new Texture2Ds (master.data [d].texture2Ds[i].name));
    31.         }
    32.         for (int i = 0; i < master.data[d].audioClips.Count; i++){
    33.             master.data [d].items [itemID].audioClips.Add (new AudioClips (master.data [d].audioClips[i].name));
    34.         }
    35.         for (int i = 0; i < master.data[d].dataSingles.Count; i++){
    36.             master.data [d].items [itemID].dataSingles.Add (new DataSingle (master.data [d].dataSingles[i].name, master.data [d].dataSingles[i].dataName));
    37.         }
    38.         for (int i = 0; i < master.data[d].dataFloats.Count; i++){
    39.             master.data [d].items [itemID].dataFloats.Add (new DataFloats (master.data [d].dataFloats[i].name, master.data [d].dataFloats[i].dataName));
    40.             for (int v = 0; v < master.data [d].dataFloats [i].value.Count; v++) {
    41.                 master.data [d].items [itemID].dataFloats [master.data [d].items [itemID].dataFloats.Count - 1].value.Add (0.0f);
    42.             }
    43.         }
    44.         for (int i = 0; i < master.data[d].dataInts.Count; i++){
    45.             master.data [d].items [itemID].dataInts.Add (new DataInts (master.data [d].dataInts[i].name, master.data [d].dataInts[i].dataName));
    46.             for (int v = 0; v < master.data [d].dataInts [i].value.Count; v++) {
    47.                 master.data [d].items [itemID].dataInts [master.data [d].items [itemID].dataInts.Count - 1].value.Add (0);
    48.             }
    49.         }
    50.         for (int i = 0; i < master.data[d].dataBools.Count; i++){
    51.             master.data [d].items [itemID].dataBools.Add (new DataBools (master.data [d].dataBools[i].name, master.data [d].dataBools[i].dataName));
    52.             for (int v = 0; v < master.data [d].dataBools [i].value.Count; v++) {
    53.                 master.data [d].items [itemID].dataBools [master.data [d].items [itemID].dataBools.Count - 1].value.Add (false);
    54.             }
    55.         }
    56.         for (int i = 0; i < master.data[d].dataStrings.Count; i++){
    57.             master.data [d].items [itemID].dataStrings.Add (new DataStrings (master.data [d].dataStrings[i].name, master.data [d].dataStrings[i].dataName));
    58.             for (int v = 0; v < master.data [d].dataStrings [i].value.Count; v++) {
    59.                 master.data [d].items [itemID].dataStrings [master.data [d].items [itemID].dataStrings.Count - 1].value.Add ("");
    60.             }
    61.         }
    62.         for (int i = 0; i < master.data[d].dataGameObjects.Count; i++){
    63.             master.data [d].items [itemID].dataGameObjects.Add (new DataGameObjects (master.data [d].dataGameObjects[i].name, master.data [d].dataGameObjects[i].dataName));
    64.             for (int v = 0; v < master.data [d].dataGameObjects [i].value.Count; v++) {
    65.                 master.data [d].items [itemID].dataGameObjects [master.data [d].items [itemID].dataGameObjects.Count - 1].value.Add (null);
    66.             }
    67.         }
    68.         for (int i = 0; i < master.data[d].dataTexture2Ds.Count; i++){
    69.             master.data [d].items [itemID].dataTexture2Ds.Add (new DataTexture2Ds (master.data [d].dataTexture2Ds[i].name, master.data [d].dataTexture2Ds[i].dataName));
    70.             for (int v = 0; v < master.data [d].dataTexture2Ds [i].value.Count; v++) {
    71.                 master.data [d].items [itemID].dataTexture2Ds [master.data [d].items [itemID].dataTexture2Ds.Count - 1].value.Add (null);
    72.             }
    73.         }
    74.         for (int i = 0; i < master.data[d].dataAudioClips.Count; i++){
    75.             master.data [d].items [itemID].dataAudioClips.Add (new DataAudioClips (master.data [d].dataAudioClips[i].name, master.data [d].dataAudioClips[i].dataName));
    76.             for (int v = 0; v < master.data [d].dataAudioClips [i].value.Count; v++) {
    77.                 master.data [d].items [itemID].dataAudioClips [master.data [d].items [itemID].dataAudioClips.Count - 1].value.Add (null);
    78.             }
    79.         }
    80.  
    81.         // Now we need to add new items for every custom variable already referencing this category
    82.  
    83.         for (int a = 0; a < master.data.Count; a++) {                                                // For each data category
    84.             for (int t = 0; t < master.data[a].items.Count; t++){                                    // For each item in the category
    85.                 for (int v = 0; v < master.data [a].items [t].dataFloats.Count; v++) {                // For each dataFloat item
    86.                     if (master.data [a].items [t].dataFloats [v].dataName == name) {                // If the dataName of the dataFloat = name
    87.                         master.data [a].items [t].dataFloats [v].value.Add (0.0f);
    88.                     }
    89.                 }
    90.                 for (int v = 0; v < master.data [a].items [t].dataInts.Count; v++) {                // For each dataInts item
    91.                     if (master.data [a].items [t].dataInts [v].dataName == name) {                    // If the dataName of the dataFloat = name
    92.                         master.data [a].items [t].dataInts [v].value.Add (0);
    93.                     }
    94.                 }
    95.                 for (int v = 0; v < master.data [a].items [t].dataBools.Count; v++) {                // For each dataBools item
    96.                     if (master.data [a].items [t].dataBools [v].dataName == name) {                    // If the dataName of the dataFloat = name
    97.                         master.data [a].items [t].dataBools [v].value.Add (false);
    98.                     }
    99.                 }
    100.                 for (int v = 0; v < master.data [a].items [t].dataStrings.Count; v++) {                // For each dataStrings item
    101.                     if (master.data [a].items [t].dataStrings [v].dataName == name) {                // If the dataName of the dataFloat = name
    102.                         master.data [a].items [t].dataStrings [v].value.Add ("");
    103.                     }
    104.                 }
    105.                 for (int v = 0; v < master.data [a].items [t].dataGameObjects.Count; v++) {            // For each dataGameObjects item
    106.                     if (master.data [a].items [t].dataGameObjects [v].dataName == name) {            // If the dataName of the dataFloat = name
    107.                         master.data [a].items [t].dataGameObjects [v].value.Add (null);
    108.                     }
    109.                 }
    110.                 for (int v = 0; v < master.data [a].items [t].dataTexture2Ds.Count; v++) {            // For each dataTexture2Ds item
    111.                     if (master.data [a].items [t].dataTexture2Ds [v].dataName == name) {            // If the dataName of the dataFloat = name
    112.                         master.data [a].items [t].dataTexture2Ds [v].value.Add (null);
    113.                     }
    114.                 }
    115.                 for (int v = 0; v < master.data [a].items [t].dataAudioClips.Count; v++) {            // For each dataAudioClips item
    116.                     if (master.data [a].items [t].dataAudioClips [v].dataName == name) {            // If the dataName of the dataFloat = name
    117.                         master.data [a].items [t].dataAudioClips [v].value.Add (null);
    118.                     }
    119.                 }
    120.             }
    121.         }
    122.     }
    In that one function, there are 3 sections that could be made smaller if I could somehow replace just a few of the phrases. In the last bit, for instance, "dataAudioClips" (stated 3 times) is the only change between each for loop.

    If I want to add a new type, I need to add a new for loop in every method that I've created. It gets tedious and boring and I'm sure there's a better way of doing it.

    _________

    In my brain, the "Easiest" way (knowing that I don't know if this is possible or if there is an easier way) would be to have another List of "Types" where all versions of phrases I may need is kept.

    AudioClips
    audioClips

    for instance, so that in the following loop:

    Code (CSharp):
    1. for (int i = 0; i < master.data[d].audioClips.Count; i++){
    2.             master.data [d].items [itemID].audioClips.Add (new AudioClips (master.data [d].audioClips[i].name));
    I would simply be able to say "For each entry in 'Types', do this code but use the proper phrase for where "audioClips" or "AudioClips" etc are. A way to do that would also keep me from having to re-do too much :D

    EDIT: The problem I'm seeing now is that "DataFloats" and "DataInts" etc, which I can make inherit from a class I"ll call "DataHolder", can't inherit from Data or Items. I don't think they can at least -- they each have a list called "value" which is of their unique type. I suppose it could have a list called "valueFloats" etc... but that would only work if I had a way of injecting multiple variable phrases into the code.
     
    Last edited: Mar 20, 2017
  17. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,738
    This feels like over-generalizing to the point of uselessness, and reinventing five or ten wheels along the way. Classes aren't a bad thing, and they're not restrictive; they're a fundamental data structure! Deciding to do away with classes is kind of like deciding that we have too many names for colors to bother learning them all, so you're just going to start referring to all colors as "blue".

    If you really want to make things super-general, though, might I recommend you use something based on JSON? (LitJSON is a good library to use for this.) It'll allow you to create your data structure in human-readable JSON code, readable from a text file (and can be downloaded from the internet, etc), and you can access arbitrarily named items on demand.
     
    infinitypbr likes this.
  18. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    I thought for a bit it might be useless. (there's a post somewhere up there about that) But then I started playing with it, and I found it to be quite easy to use. I can still create new custom classes if I need it down the line, but as far as setting up my game data, I found this to be easier than coding the classes by hand. This is, of course, coming from someone who is just now learning a lot of the things you guys already know :D
     
  19. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    I've made some headway. I have a new function, DoDeleteItem()

    Code (CSharp):
    1. public void DoDeleteItem<T>(Func<Items, List<T>> getter, Func<Items, string> stringGetter, int d, int t, string catName, int i) where T : DataHolder  {
    2.         Debug.Log ("DoDeleteItem()");
    3.         for (int v = 0; v < getter (master.data [d].items [t]).Count; v++) {
    4.             if (getter (master.data [d].items [t]) [v].dataName == catName) {                    // If the dataName of the reference matched catName
    5.                 getter (master.data [d].items [t])[v].value.RemoveAt (i);
    6.             }
    7.         }
    8.     }
    This is called from the DeleteItem() bit, which starts like this -- the commented out code is what's been replaced...

    Code (CSharp):
    1.     public void DeleteItem(string catName, int data, int i){
    2.         int removeFromCategory = 9999999;
    3.         // First delete the reference to this item from any linked category items
    4.         for (int d = 0; d < master.data.Count; d++){
    5.             for (int t = 0; t < master.data [d].items.Count; t++) {
    6.                 DoDeleteItem<DataFloats> (m => m.dataFloats, m => "dataFloats", d, t, catName, i);
    7.                 /*for (int v = 0; v < master.data [d].items [t].dataFloats.Count; v++) {
    8.                     if (master.data [d].items [t].dataFloats [v].dataName == catName) {                    // If the dataName of the reference matched catName
    9.                         master.data [d].items[t].dataFloats[v].value.RemoveAt (i);
    10.                     }
    11.                 }*/
    12.                 for (int v = 0; v < master.data [d].items [t].dataInts.Count; v++) {
    13.                     if (master.data [d].items [t].dataInts [v].dataName == catName) {                    // If the dataName of the reference matched catName
    14.                         master.data [d].items[t].dataInts[v].value.RemoveAt (i);
    15.                     }
    16.                 }
    17. .........
    (You can ignore the "stringGetter" part, that was just me trying to figure out what i can and can't do)

    So this definitely makes the code shorter, as I only need to call one line per type. It was mentioned that maybe I could have a list of types that I loop through. I need to figure that part out so I can replace the "dataFloats" part in this line with something variable: DoDeleteItem<DataFloats> (m => m.dataFloats, m => "dataFloats", d, t, catName, i);

    If so, then I think my main goal will be accomplished.
     
  20. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I have to agree with StarManta; it sounds like you're just reinventing classes. Why can't you just make a "Proficiencies" class or struct with floats for each proficiency and then a "Character" class that contains a "Proficiencies" object?

    Code (csharp):
    1. public class Proficiencies {
    2.   public float stealth;
    3.   public float climbing;
    4.   public float swimming;
    5. }
    6.  
    7. public class Character {
    8.   public Proficiencies proficiencies;
    9.  
    10.   public void TrySwimming() {
    11.     if(proficiencies.swimming > 0.5f) Debug.Log("You swam!");
    12.   }
    13. }
     
    infinitypbr likes this.
  21. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Also, if you want to keep it more generic, it sounds like a Dictionary would help you out:

    Code (csharp):
    1. var proficiencies = new Dictionary<string, float>();
    2. proficiencies.Add("stealth", 23.7f);
    3. proficiencies.Add("swimming", 123f);
    4. if (proficiencies["swimming"] > 0.5f) Debug.Log("You swam again!");
     
    infinitypbr likes this.
  22. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    Okay, I feel like I need to put this first: stop what you're doing.

    Now, let me try to explain myself a bit..

    First, I applaud your creativity. Every coder is familiar with the challenge of maintaining a large number of classes but very few actually try to abstract the repetitive pieces away.

    That said, I believe you are trying very hard to overcome some safety/sanity checks that a lot of very smart people have put into C# to protect mere mortals, like you and I, from introducing an agonizing amount of chaos to our code base. I think you've got your head so tangled up in the weeds of making your system work that you might have lost view of the larger design issues.

    Let me go back to your first paragraph, but I will call attention to a few key words and phrases.
    I don't want to put words in your mouth, but to me this sounds suspiciously like you were fighting with inheritance and dreamt up a way to combine different "categories" of things together to create the objects you needed to represent your game state. May I suggest you read up on the topic of inheritance vs composition. Next, find some examples of how to effectively use serialization in Unity to build up object graphs.

    It appears to me that you are finding your own way to composition and serialization but are trying to do it in a way that is counter to the way C# works.

    I'm afraid I don't have many reading suggestions as I'm not terribly familiar with Unity. However, I do have a thread in my history where I explore the inheritance vs composition discussion and I think the examples there may give you some ideas on alternative ways to ease your maintenance problem without resorting to reinventing the type and symbol addressing systems of C#.

    However you choose to proceed, I wish you luck!
     
    StarManta likes this.
  23. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    For dictionaries, I looked into that but saw that there were problems serializing the data.

    I started by making it just individual classes like that. But I realized that I wanted to do more -- that is, when I add new skills (what you're calling proficiencies -- I'll use a different word because I' already using proficiencies), I wanted other references to that to update.

    For instance, a CharacterClass (such as "Archer"), may have some skills that players can choose from at the start of a game. They may also have other skills that they may never learn in the game. And then they'll have a list of "proficiencies", float values, for each skill that designate how well they can use that skill -- just for math use.

    I found it was actually getting overly complicated as I started writing the classes. While I totally agree this system is more complicated behind the scenes, the editor script that allows me to edit it all is far easier and faster.

    I will look into it. I definitely am not the best coder, and C# is newer to me than UnityScript (which I don't think will be a viable solution for long). Inheritance is new to me too. I'll look into it some more, and see if I can't simplify things / work with the system :D
     
  24. jonlundy

    jonlundy

    Joined:
    Apr 15, 2016
    Posts:
    25
    I love your assets, however I think you have been given some very good advice by the other posters here. I think you need to take a step back. There are a few truths in software development
    1) It is hard to make something completely generic.
    2) The more generic it is, the harder it is.
    3) Until you have attempted to do it a few times, you will probably have failures. (I typically find that the 3rd time I make a system it is OK).

    The current wisdom is that it is better to iterate quickly and fail faster, so that you learn the problem domain better. While all the advice above is good, one extra piece I would give you is to program to the interface not the details. It looks like you are starting the details first.

    Define what your target is, and express that as a C# interface, this is a contract that you set that will implement certain behavior. Then behind this interface you can implement it however you choose, and when it is wrong, you can change it without affecting the interface too much (usually the interface also needs modifications).

    For example this is the type of thing I would implement for what you have described. I would save the more 'generic' versions for later, get something simple working first.
    (This is quick code, not production ready code).
    Code (CSharp):
    1. public class Result
    2. {
    3.     public Result(bool status, String message)
    4.     {
    5.         Success = status;
    6.         Message = message;
    7.     }
    8.     public bool Success { get; set; }
    9.     public String Message { get; set; }
    10. }
    11.  
    12. public abstract class Proficiency
    13. {
    14.     public Proficiency(string name, string description)
    15.     {
    16.         Name = name;
    17.         Description = name;
    18.     }
    19.     public string Name { get; set; }
    20.     public string Description { get; set; }
    21.     // whatever else you have.
    22.     public abstract Result use(Skill currentSkillLevel);
    23. }
    24. public class SwimSkill : Proficiency
    25. {
    26.     public SwimSkill()  : base("Swim","Swim in water")
    27.     {
    28.        
    29.     }
    30.  
    31.     public override Result use(Skill currentSkillLevel)
    32.     {
    33.         if (currentSkillLevel.skillLevel>.7)
    34.         {
    35.             return new Result(true, "You swim across");
    36.         }
    37.         else
    38.         {
    39.             return new Result(false, "You drown");
    40.         }
    41.     }
    42. }
    43. public class Skill
    44. {
    45.     public float skillLevel { get; set; }
    46.     public Proficiency AssociatedProficiency { get; set; }
    47.     public Result Use()
    48.     {
    49.         return AssociatedProficiency.use(this);
    50.     }
    51. }
    52. public interface ISkillListContainer
    53. {
    54.     bool hasSkill(string name);
    55.     Skill getSkill(string name);
    56.     void addSkill(Skill name);
    57.     void deleteSkill(Skill name);
    58.     Result useSkill(Skill name);
    59. }
    60. public interface ICharacterClass
    61. {
    62.     String name { get; set; }
    63.     ISkillListContainer DefaultSkills { get; set; }
    64.  
    65. }
    66. public interface ICharacter
    67. {
    68.     ICharacterClass myClass { get; set; }
    69.     ISkillListContainer currentSkills { get; set; }
    70. }
    71. public class SimpleSkillListContainer : ISkillListContainer
    72. {
    73.     private List<Skill> skills=new List<Skill>();
    74.     public void addSkill(Skill skill)
    75.     {
    76.         skills.Add(skill);
    77.     }
    78.  
    79.     public void deleteSkill(Skill skill)
    80.     {
    81.         skills.Remove(skill);
    82.    
    83.     }
    84.  
    85.     public Skill getSkill(string name)
    86.     {
    87.         // this is inefficent, but if you don't have a lot, and don't call it very
    88.         // often it doesn't matter.
    89.         foreach (Skill skill in skills)
    90.         {
    91.             if (skill.AssociatedProficiency.Name.Equals(name))
    92.             {
    93.                 return skill;
    94.             }
    95.         }
    96.         return null;
    97.     }
    98.  
    99.     public bool hasSkill(string name)
    100.     {
    101.         // in reality you would use a dictionary for this.
    102.         return getSkill(name) != null;
    103.     }
    104.  
    105.     public Result useSkill(string name)
    106.     {
    107.         Skill skill = getSkill(name);
    108.         if (skill!=null)
    109.         {
    110.             return skill.Use();
    111.         }
    112.         else
    113.         {
    114.             return new Result(false, "No such skill");
    115.         }
    116.     }
    117. }
    118.  
    In the above code, classes are defined for a generic proficiency a character can have, and a specific skill level a character has in that proficiency. A general interface for managing skills is defined, and then the character class and the specific character both have instances of that interface. I give a dead simple implementation of that interface (which needs error checking, and other stuff). Looping through lists like that is not optimal for large data sets, but in modern computer that means thousands of items checked very frequently.

    You could do more elaborate behaviors with lambdas and data driven architectures, but at the beginning. I recommend doing something simple that meets your needs. Then once you have a better understanding of the problem domain, go back, refactor it to meet the more general needs.

    Typically it is not feasible to create a generic structure that solves a large problem domain (character classes in an RPG is a large domain) without solving it once with non generic code and then taking those lessons as you do the next iteration.

    I also highly recommend that you read the link about 'inheritance vs composition'. That is a large subject and the linked post is very good. I used all 3 in the code above, and if I were to review it I might change how I did it.

    The most important thing is that you should keep it simple at first, get something working and try to keep 'clean' interfaces (either with a real interface, or just in how you structure the class). Then as you understand it better you can modify it.

    It seems like you are structuring your data almost as if it were tables in a database, this can be very flexible, but typically you don't want to work directly on data like that.
     
    infinitypbr likes this.
  25. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    I've decided to move forward for now, and revisit it all later. Getting this to work isn't required to get the entire system to work, and the system is something I want to use for my own game. If others find it of value on the asset store, then that's great too, but it's really for me first (as everything I produce is! :D )

    I did have one idea though. This is an editor only script. It would never be used at run time, as it only stores/manages the data, and only two classes have methods that would be used at run time. (The add/delete/rename methods wouldn't, and those are the ones that I wanted to fix.)

    What if I had a script that was changed and then run, then changed and run.

    For instance, a simple example:

    Code (CSharp):
    1. public int GreatFunction(int value){
    2.    Debug.Log("The value is an int: " + value);
    3. }
    Then this, which takes data from a list of "TypesData" that holds all the ways of saying "int", "float" etc. It creates a new string for the function. Then the actual .cs file is modified, so the method itself is changed, subsequently run.

    There may need to be a single-frame pause after the script update, though, not sure. And I'm not sure how slow this would be -- too slow and it'd get annoying.

    Code (CSharp):
    1. public void DoGreatFunction(){
    2.      //The code below is pseudo code, as I'm not yet sure how to actually accomplish it, or if it would make sense to do.
    3.      for (int i = 0; i < TypesData.Count; i++){
    4.           string newCodeString = "public " + TypesData[i].typeName + " GreatFunction(" + TypesData[i].typeName + " value){\nDebug.Log(\"The value is an " + TypesData[i].typeName + ": \" + value);\n}"
    5.           ReplaceMethodInActualScript(newCodeString);
    6.           GreatFunction(intValue);
    7.      }
    8. }
    Thoughts?
     
  26. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    So not only do you want to reinvent classes, but you want to reinvent the compiler too. ;) You are definitely overthinking this. First, it won't work, because the C# files themselves aren't used while running and are not distributed in builds; they are compiled into DLL's. Second, and more importantly, there is almost no reason you would ever need to do this... I think you might be creating solutions for problems that don't exist. Is the core of your problem just that you don't like having three separate methods for int, float, and string? Because that is really not a good reason to reinvent classes and try to include a custom C# compiler in a game. :p

    Maybe if you give us a concrete example of what a Character with Skills and Proficiencies looks like? I'm still not sure why you can't just make a class for Skill and/or Proficiencies.
     
  27. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    I accept it's entirely possible that there really is a solution that isn't something weird that I've come up with. And the code you've given me is now saved and something I want to learn more about and use when I can -- but it still doesn't solve the whole problem (though it does come closer).

    https://forum.unity3d.com/threads/c...classes-w-o-writing-code.460025/#post-3002382

    That's the thread about what I'm working on. Originally it was just going to be an RPG framework -- basically what I "used to make my game" (when it was done...I'm only just starting). But per some advice in that thread, I started abstracting some of the in-game items, treating things like Skills, which aren't physical items, as an "Item". And I really really liked the idea. I also had set up my classes in such a way that I wasn't sure if I did it right, and realized I wouldn't really know until later when I started using it.

    In the end, I realized that perhaps i can abstract away the entire thing, and when I started working with a working version, I found it to be quite fast and easy to use. Its absolutely true that typing out classes isn't hard. But it's also not fun -- at least not for me. I found this to be easier and faster.

    It all works with the code as is. Just when I want to add a new type of object, I need to add two versions of the methods to each method that deals with them -- and there's a handful, for updates, additions, deletions, checks to make sure I don't cause bugs and what not.

    Question: When you say "the C# files themselves aren't used while running and are not distributed in builds; they are compiled into DLL's", do you mean when the game is running, or when the editor is running?
     
  28. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Both. When you build a game, the C# files aren't included anywhere. In the editor, you know how after you edit a script, there's a little spinny circle thing in the lower right corner? That's Unity recompiling all the scripts and turning them into DLL's. When you actually enter play mode, it's running the code from those DLL's, not from the C# scripts themselves. You can edit the scripts while you're in play mode and Unity will try to recompile them and swap the DLL's while play mode is still happening, but in my experience it always fails miserably at trying that if your classes are complex, and you'll end up in some broken state.

    I'll take a look at that thread later and see if I have any ideas.
     
    infinitypbr likes this.
  29. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Got it. This wouldn't be used in play mode. But I suppose there'd be a brief pause after each change ,which would defeat the purpose. No one wants to wait 5 seconds :D
     
  30. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Hmmm yeah so after reading the other thread it does kinda look like you're just reinventing the whole concept of a class, with the idea that the user "won't have to write their own class", but then the user is going to have to use the Unity inspector to write the class name and a bunch of variable names and values, which to me seems objectively worse than just writing those same variable names and values in Visual Studio, where you have auto-complete, type-checking, and spell-checking. If the user wants to actually "do" anything with the classes made in the inspector, they're going to have to write their own code anyway, something like:

    Code (csharp):
    1. var damage = master.data.getItem("Spellcaster").GetInts().GetInt("Level") * master.data.getItem("Fireball").GetFloats().GetFloat("BaseDamage");
    which seems harder than just doing
    Code (csharp):
    1.  var damage = spellcaster.level * fireball.baseDamage;
    and if that's the case, then they're not really saving much time by not having to write their own class.

    But who knows, maybe some people will find it really useful. I just think the more generic you try to make it, to the point that you're basically writing a C# class in the Unity inspector and then needing to write more complicated code to access it, the less useful it is that you don't need to write the class in the code. Just don't fall down the rabbit hole where you end up duplicating all of C# in an effort to make something where the user doesn't have to write C#. ;)
     
    infinitypbr and StarManta like this.
  31. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149

    I hear you. It's a little easier than that, though :)

    Code (CSharp):
    1. float totalProficiency = master.Float("Classes", "Archer", "Stat Proficiency", "Might") * player[0].Float("Stats", "Might");
    2.  
    3. // More to the damage example, this may be what it looks like...
    4.  
    5. var damage = player[0].Int("Stats", "Level") * master.Int("Magic", "Fireball", "Base Damage");
    So it's not vastly different or more difficult. The names are used instead of numbers in case things are added/removed from the Lists as the game is made. The strings are the "name" of each category/item, and if they're changed in the data structure, the code would need to change as well.

    One thing that I didn't like classes for was for referencing other classes -- things like the proficiency. Each "Race" and "Character Class" needs to have a list of all the "Skills" and "Stats" so I can record how proficient they are in those skills/stats. Doing this with writing my own classes wasn't something I enjoyed, and I didn't find it helpful/easy -- that is, unless I'm mistaken (always a possibility), the code would end up being...

    Code (CSharp):
    1. var might = player[0].stats.baseMight * player[0].race.statProficiency.might;
    2.  
    3. // However, in this example, "might" needs to be hard-coded in the class.
    4. //If I add a new stat or skill, or change the name of one, I have to go back
    5. //into the class and the code to change all references to it.
    6.  
    7. // My version would end up looking like this...
    8.  
    9. var might = player[0].Float("Stats", "Might") * player[0].Float("Race", "Stat Proficiency", "Might");
    It's true that I don't get the auto-fill when coding it, but it's also true that it's not so complicated, I'm likely to not forget the words/phrases I used for the various components. The benefit, I found ,was that if I add a new stat or remove one or change the name of "Might" to "Strength", for instance, I only have to change the code.

    I'm also very sure that as I move forward with this game, I will certainly be adding/deleting/modifying things...there's no way I've got it all straight on paper :D

    This "Class System" thing, though, is the extent of my "changes", I guess. The goal is to just make it a bit easier to create the data structure (With flexibility for changes) and populate the data. I'd end up writing editor scripts to help with the populating data aspect, and changing the data, anyway. But now those bits are flexible enough to be used for almost any game I make, so long as there isn't any compelling reason that I *need* to use the regular class structure.

    ____

    (I will *not* be attempting this) But I also just had a thought. I wonder if this system could be modified so that a "compile" button could be pressed that creates the .cs class scripts. So that a user could set up and modify the class structure in Unity with a more flexible/forgiving GUI, and then have the system create the required regular .cs class files.

    For anyone who doesn't want to hand-code their class structure, that could be a small time saver, maybe.
     
  32. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,738
    Never underestimate your ability to forget your own code six months from now. :D Seriously, I guarantee you that you will go work on something else and then HATE yourself for forcing you to look up all these names (when intellisense could have just laid them out in a list).

    ....??? Wouldn't you "only have to change the code" if this name were only IN the code to begin with?
     
  33. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    I'm prepared for that -- the editor script shows the code for any particular entry, similar to this:



    Also, I don't use visual studio because I'm on a mac...and I think the completion features aren't as robust in monodevlop? Not sure. Really I'm not a coder by trade, so I'm often unaware of things like that.

    Maybe I'm wrong -- but if I wanted to use this code spellcaster.level * fireball.baseDamage; I'd have to either make "fireball" a reference to some entry in a "magicSpells" class, or "fireball" itself would have to be a class, right? So if I change "fireball" to "fireblast", I'd have to change the class name too (for the 2nd method). And if I wanted to remove a spell between the start of a List of "magicSpells" and "fireball", I'd have to change the reference code, or something like that?

    I could be overthinking that aspect.
     
  34. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Code (csharp):
    1.  
    2. var might = player[0].stats.baseMight * player[0].race.statProficiency.might;
    3.  
    4. // However, in this example, "might" needs to be hard-coded in the class.
    5. //If I add a new stat or skill, or change the name of one, I have to go back
    6. //into the class and the code to change all references to it.
    7.  
    8. // My version would end up looking like this...
    9.  
    10. var might = player[0].Float("Stats", "Might") * player[0].Float("Race", "Stat Proficiency", "Might");
    I think the first version is a lot clearer and simpler to be honest. And if you change the name you'd have to change all the references anyway, wouldn't you? Just instead of needing to change stats.baseMight to stats.baseStrength everywhere, you'd have to change Float("Stats", "Might") to Float("Stats", "Strength") everywhere.

    Anyway, the most important thing is having a system that works and makes sense to you, so if this way is working for you, then go for it. :)
     
    infinitypbr likes this.
  35. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,738
    I'm on a Mac. MonoDevelop's code completion is pretty great. I've only found it lacking in very specific situations (e.g. while in the middle of typing the second part of a for loop, it seems to forget everything it knows - I assume because after typing a semicolon it's trying to compile stuff but having an unpaired parenthesis breaks compilation).

    Fireball would most likely be an instance of a "Spell" class (or an "OffensiveSpell" class or something) - but more importantly, your class names and the user-facing names should almost never be the same thing. If you want to change Fireball to Fireblast, even if you have Fireball all over your code as a class, you should only need to change the user-facing name of the spell in one place.

    And if you do find the need to rename a class entirely...
    Screen Shot 2017-03-23 at 10.22.50 AM.png
    Seriously, MonoDevelop is pretty great.
     
    Last edited: Mar 23, 2017