Search Unity

Moving from spaghetti scripts to scriptable objects or similar??

Discussion in 'Scripting' started by yusefkerr, Jun 26, 2019.

  1. yusefkerr

    yusefkerr

    Joined:
    Apr 5, 2011
    Posts:
    179
    Hello, This question has a couple of different parts, I hope that's ok.

    For context, I'm a senior 3D artist with some experience scripting in c#, and I can usually get basic things to work in Unity by copying code snippets and trial/error. I'm working on a simple card game in Unity for fun, and I know I could get through this using my regular spaghetti methods, but it's a good chance to start doing things in a more regular way.

    In this particular game, the intention is for each card to present different information depending on where it is placed on a virtual board, so I basically have a spreadsheet table with the names of each card along the top and the possible positions of play down the side. There's 24 cards, 4 positions, so 96 entries in my database.

    My usual method would be to add a collision volume on each of the 24 cards, create a different trigger volume for each of the four positions, and then put something like this in a c# script for each separate card

    Code (CSharp):
    1.  OnCollisionEnter(Collision other)
    2. {
    3.      if(other.collider.name == "Position 1")
    4.      {
    5.          // Print first position text for this particular card
    6.      }
    7.  
    8. if(other.collider.name == "Position 2")
    9.      {
    10.          // Print second position text for this particular card
    11.      }
    12. }
    This is manageable for 24 cards, but what would be the best way to do this if I plan to expand the rules of the game, increase the number of cards, add more variables, etc, more easily?

    Put another way, are there general approaches for working on more advanced "databasey" things in a similar vein, like inventory systems, virtual economies, rpg stats, etc, that I could use here?

    I'd be interested in any tutorial series suggestions for this, as it's probably a big topic.

    Thanks in advance for any replies.
     
  2. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    The 'big topic' answer would probably involve separating logic from visual, so it wouldn't even use collisions at all. Think about how you would structure the raw data/relationships in a database.

    If you want to use this kind of physics approach, you could try inverting it. Make each board position call the card's PrintText function (or whatever). That way it doesn't know/care what card it is. It lets the card decide what to do with the information it is given.

    Code (csharp):
    1. public class BoardPosition : MonoBehaviour {
    2.  
    3.     public int _positionNumber;
    4.  
    5.     void OnCollisionEnter(Collision other)
    6.     {
    7.         Card card = other.collider.GetComponent<Card>();
    8.  
    9.         if (card != null)
    10.             card.PrintText(_positionNumber);
    11.     }
    12.  
    13. }
    Code (csharp):
    1. public abstract class Card : MonoBehaviour {
    2.  
    3.     public abstract void PrintText(int position);
    4.  
    5. }
    Code (csharp):
    1. public class UniqueCard : Card {
    2.  
    3.     public override void PrintText(int position)
    4.     {
    5.         switch (position)
    6.         {
    7.             case 1:
    8.                 Debug.Log("Position 1...");
    9.                 break;
    10.            case 2:
    11.                Debug.Log("Position 2...");
    12.                break;
    13.        }
    14.  
    15. }
     
  3. yusefkerr

    yusefkerr

    Joined:
    Apr 5, 2011
    Posts:
    179
    Hello, thanks for your response,

    I don't understand what you mean by "separating logic from visual", can you explain? The game uses physics to move the cards around, and I simply can't imagine a way to detect where the cards are without either unity collisions or another way to check how far a card is to the position on the board.

    I'm also keen to know how people generally use spreadsheets with unity. For example, if I have a lot of text for NPC's depending on what the player is doing, how would I store and access these more easily than putting them in a c# script?
     
  4. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    I meant data not logic.
     
  5. yusefkerr

    yusefkerr

    Joined:
    Apr 5, 2011
    Posts:
    179
    ok thanks, that makes sense. I have a few things to look up before I understand your code examples (particularly "abstract"). I did wonder if there is a good tutorial series on c#/unity that you would recommend, my go-to is to continue working through the official Unity tutorials, hopefully all will be explained.
     
  6. calpolican

    calpolican

    Joined:
    Feb 2, 2015
    Posts:
    425
    Yeah, I used to work as an artist too. You should move along, is easier than you think, and visual coding sucks. Trust me get visual studio, you're gonna love it. It's way easier than drawing a hand in perspective.
    You should get Visual Studio and complete your knolowedge on code. Checking this tutorials, specially the "intermediate ones". Take your time to absorve the knolowedge, all are really important:
    https://unity3d.com/es/learn/tutorials/s/scripting
    If you find that you can't keep up cause you're lacking some grasp of the basics, you can check this tutorial, is for kids, but really good, and has everything:
    https://www.khanacademy.org/computing/computer-programming
    Now let me walk you trhough the code posted by Stardog, so that you can see everything he's doing:


    Code (CSharp):
    1. /*"BoardPosition" is a class. Usually most classes use one script, so at frist you tend to think of classess as scripts,
    2. but remember that you can put more than one class in one script if you want.
    3. There are some classess that all of us use like int, float and string. Every class that you create, is just like those. A Vector3 is just a class with 3 variables x,y,z.
    4. When for example you say "Physics.Raycast();" Your calling a function Raycast that is in the "Physics" script.
    5. Now notice that after the name it says ": MonoBehaviour" it means that it uses the Unity made class "Monobehaviour" as a base for this other class.
    6. We say it "inherits form mono behaviour". This will allow this class to use premade methods (i.e. "functions") that exist in the MonoBehaviour class ike Awake(), Start(), Update() etc.
    7. You don't need to inherit from any class, it just add you premade stuff that you may want to share with a lot of classes.*/
    8. public class BoardPosition : MonoBehaviour
    9. {
    10. //the typical variable declaration, in this case you'll define the position on the inspector.
    11.     public int _positionNumber;
    12.  
    13.     //This is a monobehaviour inherited function. it will be called whenever somehting enters your trigger.
    14.     //Notice that it has a parameter "Collision other". Usually when you call a method, you may specify a parameter.
    15.     //In this case the monobehaviour will pass it for you: it'll pass the Collision class with several infor about what has entered the trigger, including the object's collider.
    16.     void OnCollisionEnter(Collision other)
    17.     {
    18.         //Here he declares a variable of class Card. He takes the parameter he has ("other ") and takes from that Collider script one variable: the collider. The collisioin class saves the collider in a variable called "collider"
    19.         //He then calls a method of that collider ("GetComponent") The method will return the component of class Card of this object. This method has something
    20.         //Called a generic return type, this means it can return objects of many classes, this is why he has to specifying the class of the component he wants "<>".
    21.         Card card = other.collider.GetComponent<Card>();
    22.  
    23.         /*Is possible that the object that entered the trigger, doesn't have this component GetComponent doesn't find the component. If that were the case, the variable would be empty (null)
    24.         He'll only run the next code if it doesn't equall null, because if it does unity will throw an null reference error.*/
    25.         if (card != null)
    26.             /*IFs can be used without the {} symbols if you just indent the next line,
    27.             If it passed the check, that mans he got the card. From that card, he's calling a method: PrintText, he passes the position number of this script's place on the table.
    28. to that other script (the card script). We'll see the PrintText method in the next script */
    29.             card.PrintText(_positionNumber);
    30.     }
    31.  
    32. }
    Code (CSharp):
    1.  /*//This class is defined as "abstract", with this he tells Unity that it musn't allow you to use this class as a defined object (like a component).
    2. Think of words: "animal" is an abstract word, you can't have an animal that isn't something else, like a dog, a cat, a lion, etc. Think of "Fruits" vs "Banana".
    3. //So you'll only be able to use this class thrugh some other class that inherits from it (the 3rd script).
    4. */
    5. public abstract class Card : MonoBehaviour
    6. {
    7.     //Here is the method he used in the other script. Remember that he called it from a variable
    8.     //that was of the Card class (and we're in the class Card).
    9.     public abstract void PrintText(int position);
    10.  
    11. }
    Code (CSharp):
    1. //Remember that card was abstract. Now dice is an actual unique card. As you can see it inherits from Card.
    2. //notice the ": Card" writing. So Card was inheriting from monobehaviour and this one is inheritng from card. So this one is inderectly inheriting from monobehaviour too.
    3. //It'll be able to access all of card variables and functions, and also the monobehaviour methods.
    4.  
    5. public class UniqueCard : Card
    6. {
    7.     //Wait! Didn't Card alredy had a "PrintText" method?
    8.     //"overrides" means that he's just writing over that mehtod. He may have many classes that will inherit from card. If he had many classes inheriting from card, they could
    9.     //call the common part of the method and then add their particular touch to it. In this case he's not retriving nothing from the abstract Card method, he's just overwriting it.
    10.     public override void PrintText(int position)
    11.     {
    12.         //A Switch is usually use to put all the possible cases.
    13.         //The reason why we use it, is because, in Visual Studio, you can press on the bulb icon and it'll generate
    14.         //the frame for all possible cases. In this case "position" has an infinite number of cases, but you can clamp it or something.
    15.         switch (position)
    16.         {
    17.             case 1:
    18.                 Debug.Log("Position 1...");
    19.                 break;
    20.             case 2:
    21.                 Debug.Log("Position 2...");
    22.                 break;
    23.         }
    24.  
    25.     }
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    I think the previous code is great, but here are some things I'd do differently:
    In reallity, there's no need for inheriting. So you can just merge Card and UniqueCard into one class. You can also use a list instead of a switch.
    The adavantages I can think of a list, are that you can just put the strings on the inspector without the need for hardcoding. And even if you do want to write them on the script, it's just easier with a list because you just write them between commas one after the other.
    This is an example that you could use with Stardog's BoardPosition script:

    Code (CSharp):
    1. using System.Collections.Generic;
    2.  
    3. public class Card : MonoBehaviour
    4. {
    5.  
    6.     public List<string> possibleOutcomes = new List<string>();
    7.  
    8.     public void PrintText ( int position)
    9.     {
    10.         //This return the outcome according to the position,
    11.         Debug.Log (possibleOutcomes[position + 1]);
    12.     }
    13. }
    Also, if you have any repeating text, you could just add that text to the string without needing to retype it every single time.
    For example you could say
    Code (CSharp):
    1. Debug. Log ( userName + " you've carefully placed your card in the " + position + "# position, and the result is that " + possibleOutcomes[position + 1] + ". ");
    2.  
    You can also do random introductions to genrate variation.
     
    Last edited: Jun 30, 2019