Showing posts with label Source code. Show all posts
Showing posts with label Source code. Show all posts

0 Scripting with Unity 3D: Coding "into" Unity vs. Coding "in" Unity

Looks like Unity is going to stay around for a while. It's getting better with every version. Much wanted features are added, new platforms are supported, better performance, better graphics... and the list goes on. If I were to create a start-up today, Unity 3D will be my tool of choice hands down.

However, let's not forget it's a tool and it's not going to last forever. Eventually, some other tools will flourish, embracing new technologies, new devices, new platforms, better languages, or just being cheaper or having an editor with all beautiful colors. Of course this doesn't mean you'll quit making games. This means you'll go through the painful path of learning new ways of doing the same old stuff. It's always been like this. Although there's no way to get rid of this learning curve, wouldn't it be nice to try and minimize the impact? Certainly you can't prepare yourself for an imaginary tool that doesn't exist yet, but you can do things that will help you in the future.
"Programmers who program "in" a language limit their thoughts to constructs that the language directly supports. If the language tools are primitive, the programmer's thoughts will also be primitive.
Programmers who program "into" a language first decide what thoughts they want to express, and then they determine how to express those thoughts using the tools provided by their specific language."
(Steve McConnell - Code Complete) 
I think this is true not only for languages, but for any means to an end. I'm not saying that Unity is primitive, but it's just a medium and thus it's limiting by its own nature. Knowledge and experience are never replaced but tools and technologies are, so I'd rather put technology at the service of knowledge and not the other way around.

In object-oriented programming, when some part of a program needs to interact with some other part, it's usually done in the most abstract way possible, normally through interfaces or abstract classes. A lot of the cool features found in OOP, if not all, stem from sheer common sense, so wouldn't it be wise to apply common sense for everything? My common sense dictates me that while coding into Unity, or whatever medium for that matter, I try at the same time to stay away from that medium as much as I can. I usually code as if I had to port my game to a different medium tomorrow, forcing me to separate medium-agnostic code from medium-dependent code, which in turn forces me to first think in medium-agnostic terms and thus not producing medium-dependent thoughts.

Following this premise, my common sense also tells me to not put all my code in scripts. Because not every piece of code needs to be a script. I regard scripts as if they were little wrappers encapsulating some medium-specific features, like manipulating some GameObject's transform, playing a sound, reading input, tweaking some camera's properties... you get the idea. I put the rest of the code in normal classes and make sure these classes don't reference medium-dependent assemblies. (In practice this is usually not feasible since you'll probably be using some of the medium's basic types for everything, like Unity's Vector3, but I like to think of them as coincidental dependencies)

But, why bother? Doing things like this will make you slightly slower, probably. Let's say it makes you 10% slower. If you look at the final product, it may seem you wasted 10% of your resources by doing it in a different way just for the sake of it, but it's not the case. Actually you've invested that amount in modeling, sharpening and polishing your own tools, like your way of approaching problems and the way of coming up with solutions. Not a waste at all. Even if Unity is here to stay forever.


0 Hansel Script

This is one of the first (if not the first) script I wrote using GuayScript instead of MonoBehavior. It wasn't created for anything in particular, but it'll surely end up being part of some enemy's behavior.

It keeps track of its GameObject position, and will send a notification every time it's has traveled a certain distance. The interesting part about it it's that it shows how notifications can be edited by the level designer for maximum flexibility.

/// <summary>
/// Hansel script will drop a pebble (trigger an event) whenever a certain distance has been covered
/// </summary>
public class HanselScript : GuayScript
{
    /// <summary>
    /// Distance between pebbles
    /// </summary>
    public float DistBetweenPebbles;
 
    /// <summary>
    /// Event notified everytime the distance is covered
    /// </summary>
    public Notification DropPebbleEvent = "pebbleDropped";
 
    /// <summary>
    /// Ctor,
    /// </summary>
    public HanselScript()
    {
        var lastPosition = new Vector3();
        var accDistance = 0f;
 
        OnAwake  = _properties => lastPosition = GameObject.transform.position;
        
        OnUpdate = _deltaTime =>
        {
            accDistance += (GameObject.transform.position - lastPosition).magnitude;
 
            ifaccDistance >= DistBetweenPebbles )
            {
                accDistance -= DistBetweenPebbles;
                NotifyDropPebbleEvent );
            }
 
            lastPosition = GameObject.transform.position;
        };
    }
}

0 The Message System

Messaging is at the core of any component-based system. Rather than directly calling other component's methods, you send messages to a postal address instead. If there happens to be a component listening to these messages from that address, it will receive them.

All this functionality is offered by the PostOffice class. This class works at a very low level. It doesn't know about the Guay Framework or Unity. It just knows about addresses and message ids.

To listen to a specific message, you provide an address, a message identifier and a message callback. The callback will be called whenever someone sends that message to that address.
public void Subscribe( object _address, string _message, MessageCallback _callback )
{
    ....
}

Likewise, to send a message you call the following method specifying an address, a message id and a list of parameters:
public void Send( object _address, string _message, params object[] _params )
{
    ...
}

You can also can broadcast a message through the method:
public void Broadcast( string _message, params object[] _params )
{
    ...
}

Let's see an example:
public class Player
{
    public Player()
    {
        ExampleApp.PostOffice.Subscribe( 
            "gameAddress",
            "gameStart",
            ( _event_params ) =>
            {
                Debug.WriteLine"The game has started!!" );
            } );
    }
}
 
public class Game
{
    public void Start()
    {
        ExampleApp.PostOffice.Send"gameAddress""gameStart" );
    }
}
 
public class ExampleApp
{
    private static PostOffice mPostOffice;
    
    /// <summary>
    /// Retrieves the post office
    /// </summary>
    public static PostOffice PostOffice
    {
        get { return mPostOffice ?? (mPostOffice = new PostOfficefalse )); }
    }
 
    public ExampleApp()
    {
        // a game is created
        var game = new Game();
        
        // player is created. it will subscribe to the "gameStart" message
        var player = new Player();
        
        // the game starts, sending the "gameStart" message to the address "gameAddress",
        // the message will be queued.
        game.Start();
    }
 
    public void Update()
    {
        // once per frame, the post office is updated, delivering all queued messages.
        // Player will get the "gameStart" message
        PostOffice.Update();
    }
}

This example shows what working with the typical message system looks like. However, this class is not directly exposed to the game code. Instead, the Guay Framework makes use of the PostOffice class internally, presenting a Unity-friendly interface which makes messaging really easy to use from scripts. You can find more information about this in the description of the GuayScript script.


0 A quick glance at GuayScript

GuayScript is the of the central piecea of the Guay Framework. Its main goal is to provide your scripts a clean and very intuitive API to interface with a real component-based environment. The example below shows how simple is to communicate with the system. There's no need of looking for specific components, thus avoiding the creation of ugly dependencies.

I'll explain it all in detail in future posts.

Example of a typical GuayScript script
    //
    // Health script. Makes a GameObject to have 'life'
    // 'Implements' the HealthInterface interface
    //
    public class Health : GuayScript
    {
        GameObject OtherGameObject;
        public float MaxCapacity;
        public float Amount;

        public Health()
        {
            var dead = false;
            var currentHealth = Amount;

            OnAwake = _properties =>
            {
                Messages[ HealthInterface.CmdIncrease ] = ( _id, _args ) => currentHealth += _args.Get();
                Messages[ HealthInterface.CmdDecrease ] = ( _id, _args ) => currentHealth -= _args.Get();
                Messages[ HealthInterface.CmdRefill   ] = ( _id, _args ) => currentHealth = MaxCapacity;
                Messages[ HealthInterface.CmdDie      ] = ( _id, _args ) => currentHealth = 0;
            };

            OnUpdate = _deltaTime =>
            {
                if( dead )
                    return;

                if( currentHealth <= 0 )
                {
                    dead = true;

                    // components say interesting things through the Notify function 
                    Notify( HealthInterface.EvDead );

                    // components can also say interesting things to other objects
                    OtherGameObject.Send( HealthInterface.EvDead );
                }
            };

        }
    }