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

Question How do I conveniently create multiple different scripts inherited from one base class?

Discussion in 'Scripting' started by Vefery, Jul 6, 2023.

  1. Vefery

    Vefery

    Joined:
    Feb 23, 2018
    Posts:
    119
    So I have a base class named "Action". I need to create many (30+) classes inheriting from it, for example "Eat", "Sleep", "Study", etc. As for now I just copy one of the scripts and manually edit the class name. This ended up to be very tedious (and I only created 6 scripts).
    Is there a way to do such work more efficent? Like with scriptable objects
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Do these scripts actually implement different functionality? If so then yeah, going to need to write out all these derived types.

    Worth noting, if they're plain C# classes, they don't need to be in individual script files, and you can have multiple in the one script asset without issue.

    If they're just indentifiers without any differing fuctionality, then scriptable objects would be an option too.

    Depends on your end goal/use case.

    You could do what I did once, which was to write code that writes code (something the Input System has, which I grifted the code idea from). Up to you if you think it's worth it.
     
    Bunny83 likes this.
  3. Vefery

    Vefery

    Joined:
    Feb 23, 2018
    Posts:
    119
    Yeah, the do have different functionality and they already are scriptable objecrs (well the "Action" class is) so I can't combine them really.
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Well if it helps, here's a utility thing I wrote that helps with writing scripts via code, loosely based on the 'Writer' struct tucked away inside the New Input System's code:
    Code (CSharp):
    1. #if UNITY_EDITOR // doesn't have to be editor-only
    2. using System.Linq;
    3. using System.Text;
    4.  
    5. /// <summary>
    6. /// Struct to assist with writing code assets.
    7. /// </summary>
    8. public struct CodeWriter
    9. {
    10.     #region Initialisation
    11.  
    12.     public void Initialise()
    13.     {
    14.         _buffer = new StringBuilder();
    15.         _indentLevel = 0;
    16.     }
    17.  
    18.     #endregion
    19.  
    20.     #region Internal Members
    21.  
    22.     private StringBuilder _buffer;
    23.     private int _indentLevel;
    24.  
    25.     private const int spacesPerIndentLevel = 4;
    26.  
    27.     #endregion
    28.  
    29.     #region Writing Methods
    30.  
    31.     /// <summary>
    32.     /// Begins a code block, ergo, an opening curly brace.
    33.     /// </summary>
    34.     public void BeginBlock()
    35.     {
    36.         WriteIndent();
    37.         _buffer.Append("{\n");
    38.         ++_indentLevel;
    39.     }
    40.  
    41.     /// <summary>
    42.     /// Ends a code block, ergo, a closing curly brace.
    43.     /// </summary>
    44.     public void EndBlock()
    45.     {
    46.         --_indentLevel;
    47.         WriteIndent();
    48.         _buffer.Append("}\n");
    49.     }
    50.  
    51.     public void WriteEmptyLine()
    52.     {
    53.         WriteIndent();
    54.         EndLine();
    55.     }
    56.  
    57.     /// <summary>
    58.     /// Write a line of text into the string bugger.
    59.     /// </summary>
    60.     public void WriteLine(string text, bool endLine = true)
    61.     {
    62.         if (!text.All(char.IsWhiteSpace))
    63.         {
    64.             WriteIndent();
    65.             _buffer.Append(text);
    66.         }
    67.  
    68.         if (endLine)
    69.         {
    70.             EndLine();
    71.         }
    72.     }
    73.  
    74.     /// <summary>
    75.     /// Writes an indent based on the current indent level.
    76.     /// </summary>
    77.     public void WriteIndent()
    78.     {
    79.         for (int i = 0; i < _indentLevel; ++i)
    80.         {
    81.             for (int n = 0; n < spacesPerIndentLevel; ++n)
    82.             {
    83.                 _buffer.Append(' ');
    84.             }
    85.         }
    86.     }
    87.  
    88.     /// <summary>
    89.     /// Ends a line.
    90.     /// </summary>
    91.     public void EndLine()
    92.     {
    93.         _buffer.Append('\n');
    94.     }
    95.  
    96.     /// <summary>
    97.     /// Gets the built code string.
    98.     /// </summary>
    99.     /// <returns></returns>
    100.     public string GetCodeString()
    101.     {
    102.         return _buffer.ToString();
    103.     }
    104.  
    105.     public override string ToString() => GetCodeString();
    106.  
    107.     #endregion
    108. }
    109. #endif
    And for shigs and higgles, I wrote an editor window to showcase how it can be used:
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using UnityEngine;
    6. using UnityEngine.UIElements;
    7. using UnityEditor;
    8. using UnityEditor.UIElements;
    9.  
    10. public class DerivedTypeGenerator : EditorWindow
    11. {
    12.     #region Window Opening
    13.  
    14.     [MenuItem("Editing/Derived Type Generator")]
    15.     public static void Open()
    16.     {
    17.         var window = GetWindow<DerivedTypeGenerator>();
    18.         window.titleContent = new GUIContent("Derived Type Generator");
    19.     }
    20.  
    21.     #endregion
    22.  
    23.     #region Internal Members
    24.  
    25.     [SerializeField]
    26.     private List<string> _types = new List<string>()
    27.     {
    28.         "DerivedType"
    29.     };
    30.  
    31.     [SerializeField]
    32.     private string _baseType = "BaseType";
    33.  
    34.     private SerializedObject _serializedWindow;
    35.  
    36.     #endregion
    37.  
    38.     #region UNity Callbacks
    39.  
    40.     private void OnEnable()
    41.     {
    42.         this._serializedWindow = new SerializedObject(this);
    43.     }
    44.  
    45.     private void OnDestroy()
    46.     {
    47.         this._serializedWindow.Dispose();
    48.     }
    49.  
    50.     private void CreateGUI()
    51.     {
    52.         this.rootVisualElement.Add(new Label("Derived Types:"));
    53.  
    54.         var typesProperty = _serializedWindow.FindProperty("_types");
    55.         var listView = new ListView();
    56.         listView.BindProperty(typesProperty);
    57.         this.rootVisualElement.Add(listView);
    58.  
    59.         var baseTypeProperty = _serializedWindow.FindProperty("_baseType");
    60.         var baseTypeField = new TextField()
    61.         {
    62.             label = "Base Type:"
    63.         };
    64.         baseTypeField.BindProperty(baseTypeProperty);
    65.         this.rootVisualElement.Add(baseTypeField);
    66.  
    67.         var button = new Button(GenerateTypes)
    68.         {
    69.             text = "Generate Types"
    70.         };
    71.         this.rootVisualElement.Add(button);
    72.     }
    73.  
    74.     #endregion
    75.  
    76.     #region Type Generation
    77.  
    78.     private void GenerateTypes()
    79.     {
    80.         if (string.IsNullOrEmpty(_baseType))
    81.         {
    82.             Debug.LogError("Valid Derived Type Required.", this);
    83.             return;
    84.         }
    85.  
    86.         string folder = EditorUtility.OpenFolderPanel("Select Folder", "Assets", "");
    87.  
    88.         if (!Directory.Exists(folder))
    89.         {
    90.             Debug.Log("Invalid Folder!", this);
    91.             return;
    92.         }
    93.  
    94.         foreach (string derivedType in _types)
    95.         {
    96.             var writer = new CodeWriter();
    97.             writer.Initialise();
    98.  
    99.             writer.WriteLine("using UnityEngine;", endLine: true);
    100.             writer.WriteEmptyLine();
    101.  
    102.             writer.WriteLine($"public class {derivedType} : {_baseType}", endLine: true);
    103.             writer.BeginBlock();
    104.             writer.EndBlock();
    105.  
    106.             string codeString = writer.GetCodeString();
    107.             string assetPath = $"{folder}/{derivedType}.cs";
    108.             File.WriteAllText(assetPath, codeString);
    109.         }
    110.  
    111.         AssetDatabase.Refresh();
    112.     }
    113.  
    114.     #endregion
    115. }
    116. #endif
    Super basic implementation and doesn't do much validation, but for a super quick tool it could do the job:
    upload_2023-7-6_18-30-25.png
     
    Vefery and Bunny83 like this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    Maybe a simple search and replace template system would even be easier and more versatile? So create a template with placeholders and just have a "mass template filler". Kinda like Unity's own script template (which only has one place holder for the class name).
     
  6. Vefery

    Vefery

    Joined:
    Feb 23, 2018
    Posts:
    119
    Thanks!
     
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    I'm sure I could put together something like that if I spent more time on it. Though this was a 'put together in 5 minutes' example out of code I already had on hand.
     
    Bunny83 likes this.