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

Most efficient way to relate two classes

Discussion in 'Scripting' started by skinner92, Aug 7, 2014.

  1. skinner92

    skinner92

    Joined:
    Feb 23, 2014
    Posts:
    112
    Hello there. In my game (2D shooter) I have a character carrying a weapon. In middle of the game, it is possible for the character to change her weapon, so that there is not a strict relationship between the character and her weapon. For example in my game it is also possible for the character to take some "power-up drinks" thus boosting the firing rate of the weapon.

    The way I am trying to manage this is like follows:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. namespace AssemblyCSharp
    5. {
    6.     public class WeaponClass
    7.     {
    8.         //Variables
    9.         //...
    10.         // Constructor
    11.         // ...
    12.         //Methods
    13.         //...
    14.     }
    15.     public class CharacterClass
    16.     {
    17.         private WeaponClass weapon;
    18.  
    19.         //Variables
    20.         //...
    21.         // Constructor
    22.         // ...
    23.         //Methods
    24.         //...
    25.     }
    26. }
    I don't know if there is a more logical way to do this. Maybe using inheritance?

    It is worth noting that there will be only one Character in my game, until I learn and can apply Artificial Intelligence, so that I would have "allies" that help me beating the enemies.

    Also, I have a second question: is this the right way to implement a class, i.e., inside the "AssemblyCSharp" namespace? I've noted that I am unable to use Coroutines inside this namespace because my classes don't derive from "MonoBehaviour": if I make them children of MonoBehaviour I also get an error. What can it be? Why can't I implement Coroutines?

    Thanks!
     
  2. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    You only need Monobehaviour on one of the classes. The other class can extend from it?
     
  3. Alan47

    Alan47

    Joined:
    Mar 5, 2011
    Posts:
    163
    Hi there,

    regarding your questions.

    1) Your classes look fine. Having a field inside your character class that represents the weapon slot is totally acceptable, especially when considering that your weapon is exchangeable. I don't think there is a much better way to do it. You definitly should not use inheritance for the entire thing (i.e. have a character subclass that exclusively uses shotguns, another that uses only sniper rifles...), because your *character* itself is always the same concept. Instead, have a character class that has a field which holds the weapon, just like you did, and use inheritance for weapons. For example, the base class could be "Weapon", and subclasses could include "Gun" and "Swords", and "Shotgun" could be a concrete subclass of "Gun".

    2) As far as I know you do not need to explicitly put your class into the "AssemblyCSharp" namespace. Unity will do that automatically for you. If you want, you can create your own namespaces, but unless you plan for a big project with hundreds of C# files, you will most likely not need namespaces at all.

    3) If you want to attach your code as a "component" to a Game Object in the editor later on, your class MUST derive from MonoBehaviour, either directly or indirectly (i.e. A derives from B and B derives from MonoBehaviour). The error you are getting is likely caused by your namespace declaration, but I'm not entirely sure. Try to remove the namespace around your classes and see what happens.

    Another note regarding MonoBehaviour: your classes only have to derive from MonoBehaviour if you want to use them as components in the editor. If they represent something else (utilities, data structures, ...), then you don't have to derive them from MonoBehaviour, but your MonoBehaviour classes could still reference them without problems.


    Hope this helps,


    Alan
     
    skinner92 likes this.
  4. skinner92

    skinner92

    Joined:
    Feb 23, 2014
    Posts:
    112
    Thanks for your great and detailed answer Alan.

    Your answer number 1 is already solved and you made it really clear, thanks.

    With respect to your answer number 2: this namespace is automatically created when, inside MonoDevelop, you select: File > New > File... > Empty Class. Take it a look

    With respect to number 3: the error doesn't come up due to the namespace, but with the use of Coroutines inside it. I tried removing the namespace and everything appeared to be working fine EXCEPT that I get this error:

    I can't find why this error is appearing but maybe you can help me.

    Finally I would like to ask one more thing about efficiency and correctness: at the beginning of my game, I create a new instance of my CharacterClass and WeaponClass, making them static and public so that they are accessible from anywhere inside my project. Later on, if I need to access them (say, the player changes her weapon), I just do:
    Code (CSharp):
    1. SetUp.weapon.changeWeapon(...)
    Where SetUp is attached to my Main Camera and looks like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using AssemblyCSharp;
    4.  
    5. public class SetUp : MonoBehaviour {
    6.     public static WeaponClass weapon;
    7.     public static SurvivorClass survivor;
    8.     public static ZombieClass zombie;
    9.  
    10.     public GameObject survivorGameObject;
    11.  
    12.     // Use this for initialization
    13.     void Start () {
    14.         weapon = new WeaponClass ();
    15.         survivor = new SurvivorClass (survivorGameObject, weapon, 10, 1.0f);
    16.     }
    17.  
    18.     // Update is called once per frame
    19.     void Update () {
    20.  
    21.     }
    22. }
    Is this the right way to work? Is there any better?

    Thank you again!
     
  5. skinner92

    skinner92

    Joined:
    Feb 23, 2014
    Posts:
    112
    Yes hippocoder, as Alan said you can use "successive" inheritance :)
     
  6. Alan47

    Alan47

    Joined:
    Mar 5, 2011
    Posts:
    163
    Hi,

    wow that's a lot of stuff to discuss, I'll try to structure it a bit ;)

    1) Namespaces.
    I never use MonoDevelop, so I've never encountered that behaviour. But what I can *definitly* tell you is that you do not need that namespace declaration. In MonoDevelop, on the far left hand side (the project explorer), you will see that your class is included in the "AssemblyCSharp" project anyways. If you create a new C#-File in your Unity asset browser, you'll see that it does not include the namespace declaration. This seems to be limited to MonoDevelop only, which is odd.

    2) Coroutines.
    Correctly setting up a coroutine is quite a complex task imho. Perhaps you could create a minimal example code of what you want to do and post it here? Also, a word of warning. Coroutines are a powerful concept. However, if you use them too excessively, then you will end up having no idea anymore what on earth is going on in your project. Coroutines are best used in small (<100 LoC) scripts that move bullets or play consecutive animations or similar stuff like that. I would strongly advise against the usage of coroutines in any remotely complex component!
    At the end of the day, a coroutine is "emulating" the programming style of concurrent programming, i.e. programming with multiple threads. The big advantage, especially when it comes to game programming, is that you can *pause* a thread at any time, waiting for something (e.g. wait for an animation to finish). Obviously, you can't do that in a single-threaded game because that would cause your game to freeze. I did a lot of experimenting on multi-threaded programming in Unity, here are some results in a nutshell:
    1. .NET and Mono support threads.
    2. Some people will tell you that Unity does not support threads. That's simply not true.
    3. As long as you do not change anything in a MonoBehaviour subclass, using multiple threads is perfectly fine with Unity.
    4. If you do need to modify something related to Unity (e.g. move a gameobject around) from the non-main thread, just pass a method pointer to the main thread and let it execute it.
    So if you REALLY want or need the behaviour of Coroutines, perhaps it would be better to consider using threads instead. Both are rather advanced topics though, so if you are just starting out programming, perhaps save them for later.

    3) A side note on naming conventions.
    Unless you have a very specific reason to do so, you normally should not include the suffix "Class" in your class names. So instead of "WeaponClass", the convention would be to simply call it "Weapon". It doesn't actually change something, but it's obvious that "Weapon" is a class (or interface) from the context, so usually there's no need to repeat that information.

    4) Static access.
    Okay, this one is getting a bit philosophical. First of all: if you have a field declared as "static" in a MonoBehaviour, it will *not* show up in your Unity editor inspector (as far as I know).
    Of course, accessing the player GameObject and its properties is important in many places throughout your project code. The question is: how to do that. There are several possibilities here:

    a) The static player reference.
    The idea here is that your player script includes a static variable that gets set by the Unity "Start()" method, like this:
    Code (CSharp):
    1. public class MyPlayerScript extends MonoBehaviour {
    2.  
    3.     // a static variable to refer to the current instance
    4.     public static MyPlayerScript INSTANCE = null;
    5.  
    6.     // the usual Unity Start() method
    7.     public void Start(){
    8.         // I am the current player. Note that this will simply
    9.         // override any previous player
    10.         MyPlayerScript.INSTANCE = this;
    11.     }
    12.  
    13.     public void DoSomethingSpecial(){
    14.          // do something with the player...
    15.     }
    16. }
    ... and then, any script can get the current player just like that:

    Code (CSharp):
    1. public class SomeScript extends MonoBehaviour {
    2.  
    3.     public void Update(){
    4.          MyPlayerScript player = MyPlayerScript.INSTANCE;
    5.          // note: you WILL get a NullReferenceException here if your scene has no
    6.          // GameObject with the MyPlayerScript attached! Also, if you use
    7.          // MyPlayerScript.INSTANCE in a "Start()" method, then it may be null too,
    8.          // if your current script has its "Start()" method called before the player.
    9.          player.DoSomethingSpecial();
    10.     }
    11.  
    12. }
    The advantages of this approach are:
    1. Its convenient to write
    2. Its very performant (direct reference, no search included)
    However, there are some issues with it. The static reference will not be persisted if you save the game, and you might run into some trouble when performing a level change at runtime.

    b) Using http://docs.unity3d.com/ScriptReference/GameObject.FindWithTag.html
    This method allows you to find a GameObject in your current scene that has a certain Tag attached. If you create a dedicated "Player" tag and attach it to one GameObject only, then this should work fine, even when changing levels and saving / loading the game. However, it might be less efficient than the previous way.


    I hope this wasn't too much information at once. Feel free to ask if you have further questions.
     
    skinner92 and NomadKing like this.
  7. skinner92

    skinner92

    Joined:
    Feb 23, 2014
    Posts:
    112
    Hello Allan. Really, thanks for the time you have taken to devise such an answer. It helped me in many ways.
    Let me explain why I need a Coroutine: whenever my character shots, I need to create a effect of "fire" at the end of the gun. To achieve that, I created a child GameObject and assigned a Sprite Renderer to it. Then, using my coroutine I can enable it and disable it again in just "waitTime" seconds. This is my coroutine:

    Code (CSharp):
    1. IEnumerator  showShotMuzzle(float waitTime, Transform shotMuzzle) {
    2.     shotMuzzle.renderer.enabled = true;
    3.     yield return new WaitForSeconds(waitTime);
    4.     shotMuzzle.renderer.enabled = false;
    5. }
    and I call it whenever the user clicks the screen (or touches it, since this game is meant to be played in Android):

    Code (CSharp):
    1. if (spotted) {
    2.       onSpottedPushBack(v3TouchedPos, spotted.rigidbody.transform);
    3.       StartCoroutine(showShotMuzzle (waitTime, shotMuzzle));
    4. }
    Always from the stability and performance point of view, since mobile devices generally have limited memory resources, is there any better way to get this to work without having to use coroutines? From the Unity3D Scripting Reference:

    (link: http://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html)

    This is why I thought that having just one coroutine will not have an important impact on the overall performance of my game, but who knows...

    The fact is that I get an error if I try creating and calling a coroutine from inside a class, let's say:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5.     public class WeaponClass : MonoBehaviour
    6.     {
    7.         //Variables
    8.         //...
    9.         // Constructor
    10.         // ...
    11.         //Methods
    12.         //...
    13.     }
    14.  
    15.     public class CharacterClass : MonoBehaviour
    16.     {
    17.        private WeaponClass weapon;
    18.        //Variables
    19.        //...
    20.        // Constructor
    21.        // ...
    22.        //Methods
    23.  
    24.        public void shoot(Vector3 v3TouchedPos, Transform shotMuzzle) {
    25.          StartCoroutine(showShotMuzzle (waitTime, shotMuzzle));
    26.        }
    27.  
    28.        IEnumerator showShotMuzzle(float waitTime, Transform shotMuzzle) {
    29.            shotMuzzle.renderer.enabled = true;
    30.            yield return new WaitForSeconds(waitTime);
    31.            shotMuzzle.renderer.enabled = false;
    32.        }
    33.     }
    And the error I get is:

    What do you think?

    Thanks again!
     
    Last edited: Aug 8, 2014
  8. Alan47

    Alan47

    Joined:
    Mar 5, 2011
    Posts:
    163
    Hi,

    I've just done some quick testing on your Coroutine problem. Consider the following code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class CoroutineTest : MonoBehaviour {
    5.  
    6.     // Use this for initialization
    7.     void Start () {
    8.         Debug.Log ("Before Shoot (time: " + Time.frameCount + ")");
    9.         shoot ();
    10.         Debug.Log ("After Shoot (time: " + Time.frameCount + ")");
    11.     }
    12.  
    13.     public void shoot(){
    14.         StartCoroutine (shootShowMuzzle());
    15.     }
    16.  
    17.     IEnumerator shootShowMuzzle(){
    18.         Debug.Log ("Before Coroutine starts (time: " + Time.frameCount + ")");
    19.         yield return new WaitForSeconds(2.0f);
    20.         Debug.Log ("After Coroutine ends (time: " + Time.frameCount + ")");
    21.     }
    22. }
    The above works fine without throwing exceptions. A NullReferenceException is usually easy to debug. Just include checks like:

    Code (CSharp):
    1. var enumerator = showShootMuzzle(waitTime, shotMuzzle);
    2. if(enumerator == null){
    3.    Debug.logError("Enumerator is NULL!");
    4. }
    5. StartCoroutine(enumerator);
    .... and you'll find out what exactly is NULL sooner or later. Also, include similar checks inside your coroutine code. My guess would be that your renderer component is null or something like that, but as far as I can tell, the coroutine system is not at fault here ;)


    Also, there's another way to handle this without coroutines. Create a new script that switches on the renderer in its Start() method. In the "Update()" method, it sums up the "Time.deltaTime" until your desired time has been reached. Then, it switches off the renderer again and removes itself from the game object. Instead of calling the coroutine, you would just have to add the additional script to the current game object. You will not have to take care about it any longer, as it removes itself automatically when it's done. This might not be as memory efficient as a coroutine, but imho it reveals your intentions more clearly, which is always good.

    Hope this helps,


    Alan
     
    Last edited: Aug 9, 2014
    skinner92 and NomadKing like this.
  9. skinner92

    skinner92

    Joined:
    Feb 23, 2014
    Posts:
    112
    I will have a look at it, Alan. Anyway, I am not really sure if it is a good idea to include a method, "shoot()", inside "SurvivorClass" class, because I have a separate script that handles the shooting mechanism whenever screen is touched. I will leave like it is I think.

    Thanks for all your help!