Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Making a generic class out of an inherited one

Discussion in 'Scripting' started by Daviiid, May 2, 2024.

  1. Daviiid

    Daviiid

    Joined:
    Oct 9, 2015
    Posts:
    58
    I've been trying to learn some new things like inheritance and generic things in unity. But I went wrong somewhere and can't figure it out.

    Now I have:
    A base class to inherit from
    Code (CSharp):
    1. public abstract class ShootingWeapon
    Two classes that inherit from the base one where I override the shooting implementation.
    Code (CSharp):
    1. public class SemiAutoShooting : ShootingWeapon
    Code (CSharp):
    1. public class AutoShooting : ShootingWeapon
    And there's this class:
    Code (CSharp):
    1. public class RangedWeaponBase<T> : MonoBehaviour where T : ShootingWeapon
    2. {
    3.     [SerializeField] T
    4.         shootingweapon;
    5. }
    Where I would love to inset one of the inherited classes from above in the T shootingweapon like a variable. Depending on the weapon type I'll use. And call the shooting method from the currently attached class.

    Unfortunately RangedWeaponBase class is not considered a MonoBehaviour inherited class. And I can't add it to a GameObject. Preferably it should sit on all the weapon prefabs I will create.

    Can anyone please point me in the right direction? I need RangedWeaponBase to be attachable to a GameObject. And a polymorphic way to attach one of the inherited classes to RangedWeaponBase.

    I've seen something like this done before on enemy behaviour. You could choose behaviour types and attach them to the enemy prefab. But that was a long time ago and I'm not sure I'm on the right track.
     
  2. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,482
    Unfortunately this is a Unity thing, not a C# thing. You can't mix generics with a lot of situations where Unity will be doing the serialization (prefabs, scenes, Inspector, etc.). They have purposely limited the abilities of the serializer to support only the field names and simple types including arrays, and don't depend heavily on classnames. This makes it WAY more robust in situations where the game developer has renamed or refactored a class: any class will do as long as it has fields of the right names. It makes it FAR less capable in dealing with complicated generics and inheritance cases.

    Your abstract ShootingWeapon class should handle or establish the common (shared) capabilities or data across all shooting weapons. Your RangedWeaponBase behaviour should just have a reference to a ShootingWeapon, not a generic T, and only use the abilities or data that are common to all shooting weapons. An instance of a RangedWeaponBase may put a reference to a Crossbow instance into that ShootingWeapon reference field.
     
    Bunny83 likes this.
  3. Daviiid

    Daviiid

    Joined:
    Oct 9, 2015
    Posts:
    58
    In that case I'll just make the other classes with interfaces so I can connect and change the components based on my needs. Thanks
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,559
    As has been said... you can't add the generic class since how would you tell the editor what T to put into it. Rather than create some convoluted editor system to do that, as well as a likely convoluted serialization system as well (note that generic types aren't quite types, the jitter generates them at runtime). So they just bar it.

    So you can get around this by just defining the concrete versions of the class, allowing the generic to just be boiler plate to your concretes.

    Code (csharp):
    1. public class RangedWeaponBase<T> : MonoBehaviour where T : ShootingWeapon
    2. {
    3.     [SerializeField] T shootingweapon = new();
    4. }
    5.  
    6. public class RangedWeaponSemiAuto : RangedWeaponBase<SemiAutoShooting> {}
    7.  
    8. public class RangedWeaponAuto : RangedWeaponBase<AutoShooting> { }
    Alternatively you could forego the whole generics like halley is saying. And even tap into the features of SerializeReference and create a custom editor/propertydrawer so you can pick from a list of known types:

    Code (csharp):
    1. public class RangedWeapon : MonoBehaviour
    2. {
    3.     [SerializeReference]ShootingWeapon shootingWeapon;
    4. }
    5.  
    6. public class SerRefPickerAttribute : UnityEngine.PropertyAttribute { }
    7.  
    8. [CustomPropertyDrawer(typeof(SerRefPickerAttribute))]
    9. public class SerRefPickerPropertyDrawer : PropertyDrawer
    10. {
    11.     System.Type[] _availableTypes;
    12.     GUIContent[] _availableTypeGUIContents;
    13.  
    14.     void TryInitialize()
    15.     {
    16.         if (_availableTypes != null) return;
    17.  
    18.         var alltypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.DefinedTypes).Cast<System.Type>();
    19.         _availableTypes = alltypes.Where(t => this.fieldInfo.FieldType.IsAssignableFrom(t) && t.IsSerializable && t.GetConstructor(System.Type.EmptyTypes) != null).ToArray();
    20.         _availableTypeGUIContents = _availableTypes.Select(t => new GUIContent(t.Name)).ToArray();
    21.     }
    22.  
    23.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    24.     {
    25.         this.TryInitialize();
    26.         return EditorGUI.GetPropertyHeight(property);
    27.     }
    28.  
    29.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    30.     {
    31.         this.TryInitialize();
    32.         EditorGUI.BeginChangeCheck();
    33.         int index = System.Array.IndexOf(_availableTypes, property.managedReferenceValue?.GetType());
    34.         var r0 = new Rect(position.xMin + EditorGUIUtility.labelWidth, position.yMin, position.width - EditorGUIUtility.labelWidth, EditorGUIUtility.singleLineHeight);
    35.         int newindex = EditorGUI.Popup(r0, index, _availableTypeGUIContents);
    36.         if (EditorGUI.EndChangeCheck() && newindex != index)
    37.         {
    38.             property.managedReferenceValue = newindex >= 0 ? System.Activator.CreateInstance(_availableTypes[index]) : null;
    39.         }
    40.  
    41.         EditorGUI.PropertyField(position, property, label, true);
    42.     }
    43.  
    44. }
    upload_2024-5-2_19-19-49.png

    Or yes you can do as you said:
    There's honestly a multitude of ways to tackle this.
     
    Last edited: May 3, 2024
  5. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,408
    You don't use nor need inheritance to switch your shooting style, or most other things for that matter.

    Rather than learning inheritance which has little value in game development and poses all sorts of issues and challenges, learn about data-driven development.

    You would have a Shoot class that works on a set of data, providing details like:
    • projectile to spawn
    • size of magazine
    • reload duration
    • automatic vs single-fire
    There are way more parameters imaginable but ultimately, you only need a single block of code to implement practically every weapon type.
     
    CodeRonnie likes this.
  6. Daviiid

    Daviiid

    Joined:
    Oct 9, 2015
    Posts:
    58
    Well I've made a single script to do all that in the past. The script was hundreds of lines of code. It worked, but it was unsightly. It had Automatic, SemiAuto weapons, Shotguns, different ammo types with inventory and magazines, different loading types, bullet speeds, spreads, damages, elemental dot damages and what not.

    It was getting hard to manage. So I was learning about a way to try and make it more user friendly. I've already started to cut the code into pieces and used interfaces on them to connect.

    Thanks for the suggestions.
     
  7. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,408
    It shouldn't have been unsightly. It probably wasn't actually data-driven but only branching on data. Or it lacked proper structure and applied programming best practices. Perhaps data was set up incorrectly, like giving each type of weapon its own set of data.

    The shotgun for instance can be set up to fire a single projectile where the projectile itself spawns multiple buckshots, or it could be part of the shooting where each weapon has an entry "number of projectiles to spawn" which for most weapons will be 1, or it could be "if (type == shotgun) then <custom code and custom data>".