Categories

Delegate (or callback) really useful !!

In the evening of this day, I took a look at the code figuring the way to separating components and did refactoring, just to prepare Junk Master to be ready for the next move when designing a new experience of levels.

Also as I have heard about delegate from my friend Dome, at first I think it’s not that big deal for my game to use it, but I face it now, it’s time to integrate it into a game.
As a particular game could even expand into a real complex nature, some techniques to lower the difficulty in implementing are need. Okay, let now stating about the case we gonna talk about. Imagine your game as multi-levels and each level is implemented inside a Scene class (simply think it as scene). One thing which is important when implement multi-level game is that the common group of components/entites/systems should be grouped together and place in one location, programmers don’t want to copy and paste all the code all around redundant in each scene right?

First see the interface of Junk Master game below.

UI in-game of Junk Master

UI in-game of Junk Master

As see from the image above, there are bunch of the common entities which are going to be the same in all the levels. They are 4 trashes, 3 weapons, buddy, pipe, upper/lower interface, and different form of mouse cursors. By pushing some effort and time in refactoring all those common entities into another class, let called it Base Class will do the trick at first, but the real problem appears as when updating all those entities and doing logic code relating those with some specific entities/components in each level (thus each scene class) will require programmers to find some ways to inject section of code to be between those code in Base Class.
I assume here that in most case the game or system is quite complex as it’s not easy to well separate the order of updating section into different chunk of code.

Thus we have 3 options here so far.
1.) Not do refactoring, but copy all those base logic to all scene class.
2.) Separate the logic code of Base class into methods, the number of methods to create is based on the complexity of the logic and the way to update itself, and then let another class to call its own method in between Base Class’s calls or extend Base Class and override the user method to implement user’s own functionality then finally call it in between as usual. See some examples below.
Ex. 2.1 I know you are all familiar with the following drawing code in XNA. It’s simple 2d stuff.

...
spriteBatch.Begin();
spriteBatch.Draw(...);
myComponent1.Draw(...);
myComponent2.Draw(...);
spriteBatch.End();
...

The above system of drawing pipeline can be well separated, thus this option can be possibly selected.
But note from the code that the reason to not separate the calling of Begin(), and End() in each component is to demonstrate the example, and also by calling Begin() and End() and do a large bunch of drawing stuff in between, this can save time a lot as the context and states of drawing pipeline won’t be changed too many time.

Ex. 2.2 This example demonstrate the updating logic which can be well separated and easy to implement.

...
mySystem.PreUpdate();
mySystem.UserUpdate(...);  <--- overrided method
mySystem.PostUpdate();
...

Note that in order to successfully do like that, users need to extend that particular system class which provided the user-method to override, then finally if need users just call their own overrided user-method in between.

For Ex. 2.2, it requires users to know some underlying implementation, and it partially exposes the internal system to users as users need to extend the class.

Now from the above 2 examples, let’s think back to our case of multi-level (scene class) implementation.
Keep in mind that these 2 examples need the logic to be possible to separate as methods, thus letting users to inject their method in between as see from the examples above.
Ex. 2.1 is rare in multi-level implementation, only a simple game possibly uses this approach. Ex. 2.2 is quite possible and can be use with no fust but as stated above it requires users to extend the class and put their own functionality. In this case, it’s some kind of overlapping in the sense as every level (scene) is the Base Class functionality in itself, not well separated of bigger picture of components in the sense, and some internal logic may expose to users. But this approach gives a really useful for each level to be complete in its own single class, do all the stuff in only 1 file is better in the sense of level programming.

3.) Use delegate or callback.
By using this approach all the chunk of implementation of the Base Class is inside its own class, usually I make it as Singleton, so it’s well separated and we can ensure that only one instance of Base Class exists. In Base Class, there is one thing special which is user-defined logic code section will be separated by using delegate or callback method.
When create a new level in Scene class, users just need to create methods for update in each particular logic, then set those method via the set-method provided by Base Class, thus this will in turn links the signature of method and set them to be a callback method when executing in the Base Class’s space. This is very clean approach !! If confuse see the examples below.

Ex. 3.1
Base Class

...
// Cursor
// Hand Cursor affect by enemies delegate
public delegate void ParameterlessDelegate();
private static ParameterlessDelegate handCursorAffectedByEnemiesInsideBoundCallBack = null;
private static ParameterlessDelegate handCursorAffectedByEnemiesOutsideBoundCallBack = null;
 
/// 
/// Sets the callback method for the hand cursor (inside-bound) affected by enemies.
/// 
/// Callback method to set.
public static void SetHandCursorAffectedByEnemiesInsideBoundCallBack(ParameterlessDelegate callback)
{
    handCursorAffectedByEnemiesInsideBoundCallBack = callback;
}
 
/// 
/// Sets the callback method for the hand cursor (outside-bound) affected by enemies.
/// 
/// Callback method to set.
public static void SetHandCursorAffectedByEnemiesOutsideBoundCallBack(ParameterlessDelegate callback)
{
    handCursorAffectedByEnemiesOutsideBoundCallBack = callback;
}
...

First of all we declare a delegate signature

// Hand Cursor affect by enemies delegate
public delegate void ParameterlessDelegate();

Then we declare our delegate variable to hold the pointer to the method (which will be set by users).

private static ParameterlessDelegate handCursorAffectedByEnemiesInsideBoundCallBack = null;
private static ParameterlessDelegate handCursorAffectedByEnemiesOutsideBoundCallBack = null;

I have declared 2 for it as in the logic updating section, there are 2 different location to be able to inject user-defined code there.

Next we must provide the way to set those delegate.

/// 
/// Sets the callback method for the hand cursor (inside-bound) affected by enemies.
/// 
/// Callback method to set.
public static void SetHandCursorAffectedByEnemiesInsideBoundCallBack(ParameterlessDelegate callback)
{
    handCursorAffectedByEnemiesInsideBoundCallBack = callback;
}
 
/// 
/// Sets the callback method for the hand cursor (outside-bound) affected by enemies.
/// 
/// Callback method to set.
public static void SetHandCursorAffectedByEnemiesOutsideBoundCallBack(ParameterlessDelegate callback)
{
    handCursorAffectedByEnemiesOutsideBoundCallBack = callback;
}

Note above that the signature that we declared as the first line can be used for other methods too as long as that method doesn’t return value and doesn’t accept any parameter.

Base Class: Logic Updating Section

...
if (Base.CursorManager.CurrentCursorState == CursorManager.CursorState.HAND)
{
	// we also checked for the mouse on bottom interface (safe area) -> divide for accuracy
    // (using xnview to bound rectangle)
    if (!Engine.MouseManager.IsOnSight(0, 668, 256, 100) &&
        !Engine.MouseManager.IsOnSight(243, 714, 592, 54) &&
        !Engine.MouseManager.IsOnSight(858, 557, 165, 211) &&
        !Engine.MouseManager.IsOnSight(920, 495, 82, 89) &&
        !Engine.MouseManager.IsOnSight(277, 640, 87, 76) &&
        !Engine.MouseManager.IsOnSight(420, 640, 87, 76) &&
        !Engine.MouseManager.IsOnSight(568, 640, 87, 76) &&
        !Engine.MouseManager.IsOnSight(716, 640, 87, 76))
    {
        if (handCursorAffectedByEnemiesInsideBoundCallBack != null)
            handCursorAffectedByEnemiesInsideBoundCallBack();
    }
 
    if (handCursorAffectedByEnemiesOutsideBoundCallBack != null)
        handCursorAffectedByEnemiesOutsideBoundCallBack();
}
...

That should explains why I named those two variables like that, the above code means that if the current cursor in the game is hand type, and if the mouse position is over that particular areas then calling the method pointed to by handCursorAffectedByEnemiesInsideBoundCallBack , and as if the cursor is just hand type also call the method pointed to by handCursorAffectedByEnemiesOutsideBoundCallBack.

Now let’s we go to the Scene Class.

Scene Class

...
Base.SetHandCursorAffectedByEnemiesInsideBoundCallBack(new BaseGameSessionScene.ParameterlessDelegate(HandInsideAffectedCallBack));
Base.SetHandCursorAffectedByEnemiesOutsideBoundCallBack(new BaseGameSessionScene.ParameterlessDelegate(HandOutsideAffectedCallBack));
...

The above code just set the user-method (see below) mapping to delegates we saw earlier in Base Class.

private void HandInsideAffectedCallBack()
{
    // check collision for all ants
    foreach (Ant a in ants)
    {
        if (Engine.MouseManager.IsOnSight((Entity2D)a) &&
            a.CurrState != Ant.State.Death &&
            a.CurrentAnimation != a.GetAnimationString(Ant.AnimationSet.BEINGBLOWED) &&
            a.CurrentAnimation != a.GetAnimationString(Ant.AnimationSet.BEINGSPRAYED))
        {
            Base.CursorManager.Hand.Tint = Color.Red;
 
            //deduct point
            Base.HGauge.AddPoint(Spec.Rule.PointSystem.AntDeductOver.Happiness);
        }
    }
 
    // check collision for all cockroaches
    foreach (CockRoach c in cockRoaches)
    {
        if (Engine.MouseManager.IsOnSight((Entity2D)c) &&
            c.CurrState != CockRoach.State.Death &&
            c.CurrentAnimation != c.GetAnimationString(CockRoach.AnimationSet.BEINGBLOWED) &&
            c.CurrentAnimation != c.GetAnimationString(CockRoach.AnimationSet.BEINGSPRAYED))
        {
            Base.CursorManager.Hand.Tint = Color.Red;
 
            //deduct point
            Base.HGauge.AddPoint(Spec.Rule.PointSystem.CockRoachDeductOver.Happiness);
 
            //play hit sound
            if (SM.BeingAttacked.IsStopped)
                SM.BeingAttacked.Play();
        }
    }
}
 
private void HandOutsideAffectedCallBack()
{
    // check collision for all birds
    foreach (Bird b in birds)
    {
        if (Engine.Utility.IsCollide(Base.CursorManager.Hand, (Entity2D)b) && b.IsAttack)
        {
            Base.CursorManager.Hand.Tint = Color.Red;
 
            //deduct point
            Base.HGauge.AddPoint(Spec.Rule.PointSystem.BirdDeductOver.Happiness);
        }
    }
}

And the code defined in the Scene Class by users will be invoked in that particular location we just saw in the Base Class. Don’t pay attention in the detail of above 2 methods too much, just know that the detail is up to the users, and inject it in the Base Class.

What we saw from the 3rd approach by using delegate or callback is that we could make a code cleaner, well separated in the sense, not exposed the internal stuff from Base Class, user pays only attention in the user’s space (his/her own class), can solve not-too-extreme-complexity-logic, and I am happy to use this :)

By any means which approach to choose is depend on the task or work at hand, and of course the situation in that occasion. If you have enough time, then you better choose 3rd approach. If you don’t have much time and want to test only a single level, then go for 1st option then further modify it later to use either 2nd or 3rd approach. For 2nd approach, it’s a bit in between from 1st and 3rd approach.

I must thank you if you managed to read to this far. Sorry for very long post !
One last note, the exception is still there, delegate or callback may not be able to solve your extreme of complexity such as the ordering of drawing many entities/components on screen (in which it must be able to customized in any time, much to say), so just go for a simple approach as you see fit is better.

Thanks again, and we’ll meet again soon.

Haxpor

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">