4
\$\begingroup\$

I am trying to use component based design in Unity3d v5. If I have 2 separate C# scripts attached to enemy ships like below:

  1. Script1.shipDead() - Remove game object and show explosion animation
  2. Script2.showPoints() - Destroyed ship shows points ex: +100 (later this will fade away)

How can I perform showPoints() function after shipDead() function has been performed, as a callback?

I can't put both actions in the same function, as some enemies will not have points. Some enemies will leave power ups/ small enemies/ bombs after being destroyed.

And I don't want to duplicated the same code in 4 functions.

Like :

  1. function 1: destroy ship (nothing more)
  2. function 2: destroy ship + show points
  3. function 3: destroy ship + spawn small enemies
  4. function 4: destroy ship + show points + spawn bomb

(As you can see above, there are many different possibilities / combinations I can come up with)

I need to separate functions and isolate them to do individual tasks. As shipDead() will not do anything related to showPoints() or showPowerUp() functions.

My question is how can I perform functions sequentially? One after another, like a callback function?

Like this:

  1. run Script1.DestroyShip() first;
  2. after that run Script2.ShowPoints();
  3. and finally run Script3.SpawnBomb();

Please provide a solution that will not violate the following:

  1. Components needs to be reusable (I can drag and drop the scrip in to other enemies/ships)
  2. Code shouldn't be duplicated
  3. Loosely coupled (Lesser dependancies)

Please help me on this, Thanks in advance!

\$\endgroup\$
3
  • \$\begingroup\$ Why not use a virtual method? Override it to add extensions. Although, I guess that violates the dependency thing. I think your components are too small. I know splitting a game object up into little reusable functionality like this sounds appealing, but in practice I've found it's much more trouble than it's worth, especially when game objects with multiple states and animations are involved. The fact that all these things need to be executed in a sequence upon a specific event is enough to tell you they have some sort of dependency on something. \$\endgroup\$
    – Ben
    Mar 27, 2015 at 12:05
  • \$\begingroup\$ Thanks for the quick reply ben, with your experience do you encourage to use standard OOP, inheritance and etc over this "component based design". Do you find OOP more easier to implement in practice? \$\endgroup\$
    – stackflow
    Mar 27, 2015 at 14:22
  • \$\begingroup\$ I think it certainly is a great way to go about things, just not in this way. Trying to completely decouple tiny functionality like this is very difficult and probably not worth the time. For the approach I'd recommend, look at Unity's example components( there's a heap in the Unity 5 demos ) or at the examples in Unreal 4( there's a heap here too ). Really, components in this sense should provide one complete package, that's more than a single method. Think of transform, RigidBody, Renderer, etc. They can be huge pieces of code, but are still very reusable. \$\endgroup\$
    – Ben
    Mar 27, 2015 at 15:02

3 Answers 3

1
\$\begingroup\$

I see two solutions to your questions.

1) Use the SendMessage(string methodName) function. It will call the given method on every components of the gameObject. (More info here: http://docs.unity3d.com/ScriptReference/Component.SendMessage.html).

Here is an example:

public class Script1 : MonoBehavior
{
    public void DestroyShip()
    {
        /* Destroy the ship here. */
        SendMessage("OnShipDestroyed");
    }
}

public class Script2 : MonoBehavior
{
    public void OnShipDestroyed()
    {
        this.ShowPoints();
    }

    public void ShowPoint()
    {
        /* Show points here. */
    }
}

2) Use .Net events. Receiver registers to an event triggered when the ship is destroyed. Here is an example as well:

public class Script1 : MonoBehavior
{
    public event System.EventHandler OnShipDestroyed;

    public void DestroyShip()
    {
        if (this.OnShipDestroyed != null)
        {
            this.OnShipDestroyed.Invoke(this, new System.EventArgs());
        }
    }
}

public class Script2 : MonoBehavior
{
    void Awake()
    {
        this.GetComponent<Script1>().OnShipDestroyed += OnShipDestroyed;
    }

    private void OnShipDestroyed
    {
        this.ShowPoint();
    }

    public void ShowPoint()
    {
        /* Show points here. */
    }
}

Note that this solution creates a dependency of Script2 to Script1.

Hope it helps.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Not much point using events in Unity for the purpose of decoupling components if you're going to keep the dependency. You should definitely mitigate as much dependency as possible by instead just even using a single EventManager Singleton class etc. You'll quickly run into spagetti code adding listeners to individual scripts etc. \$\endgroup\$
    – DanoThom
    Mar 27, 2015 at 13:12
  • \$\begingroup\$ Thanks! It seems "component based design" is not the best approach ever. Do u think for big projects this "component based design" is not a good idea? As it seems to be creating more problems for me. \$\endgroup\$
    – stackflow
    Mar 27, 2015 at 14:27
  • \$\begingroup\$ I totally agree with DanoThom that the second solution leads to dependencies and puzzle-programming. The first solution is more suitable for the component design you are trying to achieve as the SendMessage function does not require a recipient, and do not require to know the type of the script that invoke it. \$\endgroup\$
    – Hef
    Mar 27, 2015 at 16:58
  • 1
    \$\begingroup\$ @Hef You just have to know the EXACT name of the message in order to respond to it. Sounds like a dependency to me. Just a very poorly expressed one. \$\endgroup\$
    – Ben
    Mar 27, 2015 at 17:22
1
\$\begingroup\$

I'm going to try and write some example code using delegates since I think they might do what you want. Code is mostly taken from the delegate section of MSDN

using UnityEngine;
using System.Collections;

namespace MyNamespace
{
    public class ShowPoints : MonoBehaviour
    {

        public void showPoitsn()
        {
            Debug.Log("ShowingPoints");
        }
    }
}

using UnityEngine;
using System.Collections;


namespace MyNamespace
{
    public class DestroyShip : MonoBehaviour
    {


        public void Destroy()
        {
            Debug.Log("ShipDestroyed");
        }
    }
}
using UnityEngine;
using System.Collections;

namespace MyNamespace
{
   public enum BehaviourType
    {
        JustDestroy,
        DestroyAndShowPoints,
        ShowPoints
    }
    public class DelegateHandler : MonoBehaviour
    {
        private DestroyShip dShip;
        private ShowPoints sPoints;
        public  BehaviourType type;
        delegate void MyDelegate();
        MyDelegate destroy, showPoints,c;
        void Start()
        {
            dShip = GetComponent<DestroyShip>();
            sPoints = GetComponent<ShowPoints>();

            destroy = new MyDelegate(dShip.Destroy);
            showPoints = new MyDelegate(sPoints.showPoitsn);

            ComposeDelegates();
        }
        //Now we need some logic to handle the composing of a new delegate that can invoke the functions depending on our cases

        void ComposeDelegates()
        {
           switch(type)
            {
                case BehaviourType.JustDestroy:
                    c = new MyDelegate(destroy);
                    break;
                case BehaviourType.DestroyAndShowPoints:
                    c = new MyDelegate(destroy + showPoints);
                    break;
                case BehaviourType.ShowPoints:
                    c = new MyDelegate(showPoints);
                    break;
            }

            c();

        }

    }
}

How this works is that you attach all the components to the gameObject and the select what type of behaviour you want from the DelegateHandler. In my opinion this covers your criteria and its easy to extend. Not sure about the dependancy criteria you have.

\$\endgroup\$
1
  • \$\begingroup\$ Interesting approach. But this would create lots of boilerplate code I imagine. \$\endgroup\$ Nov 25, 2016 at 16:40
1
\$\begingroup\$

Well here is the solution: I'm not judging your approach, but it's not right obviously. You aren't following the SOLID principles of OOP. First one of them is S - Single Responsibility. Currently, your Script2 is doing operations that should not be there, he should not be responsible for giving any points to the player when something dies.

It's the responsibility of enemy to give you points for their death unless you collect these points like objects, but it's not the case right now. It always depends on the game. But in your case, it's easier to create a system when enemy gives you these points.

Now imagine this architecture - you have many types of enemies Pirate, OrangePirate, BlackSailsPirate - every one of them has its own reward for being defeated. What you need to do is to create an OnDeathevent. As I remember something like this public Action OnDeath; and also a method for every Enemy Die(), now that you have it just do in Start() OnDeath += DeathReward;. Call it in Die() { if (health < 0) { OnDeath(); Destroy(gameobject);.......... } }. - And do all this stuff in parent class Enemy (I hope you understand inheritance(OOP)). Also in Enemy create a public virtual void DeathReward() { throw new NotImplementedException(); } This will call an exception if you haven't implemented the method in your new enemy type.

Now when you create a new type of enemy - just inherit and implement this method by public override void DeathReward() { "give 150 points and leave a bomb" }. - It's the best approach I know. When you destroy an enemy - you, of course, have a method where you destroy it - just call Die there instead. I hope you have some kind of health system + TakeDamage(uint quantity) method where you check health.

Performance = much better than checking in switch or if. It's actually the best because everything computer does points to chunks of code in RAM.

Let me know if you didn't understand something, I will try to clarify it.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .