Páginas

terça-feira, 11 de novembro de 2014

Unity and Components Orientation Tutorial - Part Three

This is the third part of this series about Component Orientation and Unity. In part two, I've made a very simple system so that things could automatically aim at anything tagged with a TargetableBehavior component. In this part, I'll make a very simple movement system, and add components that allow the player and AI to move their ships.

The movement I want is very simple. The object can be accelerated and decelerated, and asked to turn facing towards some angle. Of course, there's also attributes like current direction (or angle) and current speed.

To make things nicely separated in components, I will need three behaviors. The first one, and most important one, is a movement behavior. It will receive commands and move objects as needed. This behavior is the basis of our moving things. The other two behaviors the player and AI, that sends movement orders to this movement behavior. Even thought I could use inheritance here and make the player controller and AI inherit from the base movement, I won't do this. With components, it is easier to simply change the movement orders issuer for whatever need I might have (i.e give player control of another ship, changing the AI movement style at runtime, etc).

Naturally, to allow components to communicate with each other without needing to know their existence or types, SendMessage will be needed. As always, since SendMessage takes a string as the message to be sent, it is easy to make invalid code, or break some scripts when we rename methods. This time I'll go a little bit further in safety measures than I did in part two. I'll use C# Interfaces!

/// <summary>
/// Interface needed by moveable objects. Theres not much
/// sense for this Interface, since it is more likely there will
/// be one moveable behavior. The main reason for this interface,
/// in this case, is to make SendMessage less error prone. For
/// example, if we decide to rename or change parameters of these
/// methods.
/// </summary>
public interface IMovable {
    void TurnToAngle(float degrees);
    void Accelerate(float accelerationPower);
    void Decelerate(float decelerationPower);

    float CurrentSpeed { set; get; }
    float CurrentAngle { set; get; }
}

public class MovMsgs {
    public const string TurnToAngle = "TurnToAngle";
    public const string Accelerate  = "Accelerate";
    public const string Decelerate  = "Decelerate";
}


In the script above, there is an Interface that will indicate all the methods needed by any “mover” component that might be done. The class bellow, MovMsgs, simply contains constant strings that are the ones to be used within SendMessage parameters. This way, if I ever need to rename the methods and messages, I'll do it in the Interface and change the related constant strings in the MovMsgs, thus reducing the likelihood of breaking things when bug-fixing or code re-factoring. I modified the “targetables” scripts to use Interfaces as well, but I won't list the code here, for space. You can try it as an exercise or download the source code.

Now, there's another trick I'll use for the movement behaviors. I plan to have a single movement behavior, but I can't be sure that this will always be true in the lifetime of this project. With this in mind, I'll do some “flexibility” code that will make it easier to add more movements in the future. I want all movement behaviors to be seen as the same type. I'll be using the generalization concept of Object Oriented Programming, where child classes are the same type of their parent.

using UnityEngine;
using System.Collections;



/// <summary>
/// Base component for movement behavior. The main reason for
/// this component to be inheritable is that I want that other
/// components can find any type of movement behaviors with a
/// simple GetComponent.
/// </summary>
abstract public class MovementBehaviorBase : MonoBehaviour, IMovable {
    abstract public void TurnToAngle(float degrees);
    abstract public void Accelerate(float accelerationPower);
    abstract public void Decelerate(float decelerationPower);

    abstract public float CurrentSpeed { set; get; }
    abstract public float CurrentAngle { set; get; }

    // Yes, no virtual or abstract methods for Unity methods such as
    // Awake or Update. This class is not meant to hold any behavior
    // or initialization by itself.
}


The MovementBehaviorBase is our model to any movement behavior that might be done. The component itself is abstract, blocking you from instantiating or putting it in GameObjects. Also, the abstract methods need to be defined on child components so, actually, this is a “model” component for future others to follow. If you don't know what abstract classes and methods means, or don't know about inheritance and interfaces, I advise to do some researching. These concepts are quite “big” and would require a some hours of study.

About the movement itself, it is coded on another component:

using UnityEngine;
using System.Collections;



/// <summary>
/// Basic movement simply mave forwards with the "current"
/// angle and speed.
/// </summary>
[AddComponentMenu("Scripts/Movement/Basic Movement Behavior")]
public class BasicMovementBehavior : MovementBehaviorBase {
    // Movement attributes
    [SerializeField] protected float m_MaxSpeed;
    [SerializeField] protected float m_MinSpeed;
    [SerializeField] protected float m_MaxTurnSpeed;
    [SerializeField] protected float m_MaxAccel;
    [SerializeField] protected float m_MaxDecel;

    // Movement variables
    protected float m_CurrentAngle; // Degrees per second
    protected float m_CurrentSpeed; // World units per second

    // Small optimization by caching
    protected Transform m_Transform;
    protected Rigidbody2D m_RigidBody2D;




    public override float CurrentAngle {
        get { return(m_CurrentAngle); }
        set { MoveRotation((m_CurrentAngle = value)); }
    }

    public override float CurrentSpeed {
        get { return(m_CurrentSpeed); }
        set { m_CurrentSpeed = Mathf.Clamp(value, m_MinSpeed, m_MaxSpeed); }
    }



    protected void Awake() {
        m_Transform = this.transform;
        m_RigidBody2D = this.rigidbody2D;
    }



    protected void Update() {
        // Move the object
        Vector3 pos = m_Transform.position;
        pos.x += Mathf.Cos(m_CurrentAngle * Mathf.Deg2Rad) * m_CurrentSpeed * Time.deltaTime;
        pos.y += Mathf.Sin(m_CurrentAngle * Mathf.Deg2Rad) * m_CurrentSpeed * Time.deltaTime;
        MovePosition(pos);
    }



    /// <summary>
    /// Turns the object towards an angle
    /// </summary>
    public override void TurnToAngle(float degrees) {
        m_CurrentAngle = Mathf.MoveTowardsAngle(m_CurrentAngle, degrees, m_MaxTurnSpeed * Time.deltaTime);
        MoveRotation(m_CurrentAngle);
    }



    /// <summary>
    /// Accelerate with the given acceleration power
    /// </summary>
    public override void Accelerate(float accelerationPower) {
        accelerationPower = Mathf.Clamp01(accelerationPower);
        m_CurrentSpeed += accelerationPower * m_MaxAccel * Time.deltaTime;
        m_CurrentSpeed = Mathf.Clamp(m_CurrentSpeed, m_MinSpeed, m_MaxSpeed);
    }



    /// <summary>
    /// Decelerate the specified deceleration power
    /// </summary>
    public override void Decelerate (float decelerationPower) {
        decelerationPower = Mathf.Clamp01(decelerationPower);
        m_CurrentSpeed -= decelerationPower * m_MaxDecel * Time.deltaTime;
        m_CurrentSpeed = Mathf.Clamp(m_CurrentSpeed, m_MinSpeed, m_MaxSpeed);
    }



    protected void MovePosition(Vector3 position) {
        if(m_RigidBody2D != null) {
            m_RigidBody2D.MovePosition((Vector2) position);
        } else {
            m_Transform.position = position;
        }
    }



    protected void MoveRotation(float rotation) {
        if(m_RigidBody2D != null) {
            m_RigidBody2D.MoveRotation(rotation);
        } else {
            Vector3 angles = m_Transform.eulerAngles;
            angles.z = rotation;
            m_Transform.eulerAngles = angles;
        }
    }
}


The code is straightforward and very simple to understand. It allows the object to be turned around, accelerated and decelerated, there's a maximum turning speed, and maximum and minimum movement speeds. Finally, some specialized code to treat rotation and movement differently if there was a RigidBody2D in the object at awake. As a rule of thumb, if your GameObject has one of either RigidBody or RigidBody2D component, you're better off moving it with physics or the rigid body methods MovePosition and MoveRotation, instead of directly changing the transform component of the object. Directly changing the transform in their presence might cause unexpected behavior, wrong or broken physics, and even miss collision detection.

The new stuff introduced in the code above is the AddComponentMenu attribute. It says to Unity where you want your component to be listed in the menus when trying to add it to a GameObject. This is solely to keep things organized, and help other team members (i.e designers) to quickly find components that they need and can use. The right ones, mind you! You should make use of the opportunities to make the non-programmers developers life easier.

The code to move the player is pretty straightforward now, too.

using UnityEngine;
using System.Collections;



/// <summary>
/// Detect input from the player and pass over to a MovementBehaviorBase
/// child instance.
/// </summary>
[AddComponentMenu("Scripts/Movement/Player Movement Controller")]
public class PlayerMovementController : MonoBehaviour {
    protected MovementBehaviorBase m_Movement;



    private void Awake() {
        m_Movement = this.gameObject.GetComponent<MovementBehaviorBase>();
    }



    private void Update() {
        if(m_Movement != null) {
            // TODO : GET RID OF MAGIC VALUES
            if(Input.GetKey(KeyCode.W)) m_Movement.Accelerate(1.0f);
            if(Input.GetKey(KeyCode.S)) m_Movement.Decelerate(1.0f);

            float currentAngle = m_Movement.CurrentAngle;
            if(Input.GetKey(KeyCode.A)) m_Movement.TurnToAngle(currentAngle + 90.0f);
            if(Input.GetKey(KeyCode.D)) m_Movement.TurnToAngle(currentAngle - 90.0f);
        }
    }
}


At Awake, I attempt to find a movement behavior within the same object. I do a component search for MovementBehaviorBase, but any component that inherits it will be returned – and right now, it can only be an instance of BasicMovementBehavior.

On Update I simply check if any movement behavior was found in the Awake and, if true, issue movement orders accordingly to the current user input. I could, of course, have reduced the coupling between the PlayerMovementController and BasicMovementBehavior components and used SendMessages instead, but my desire here was to show that the inheritance works as expected. This is important for components that might need more information about their movement, such as angle and speed (I.e an aiming AI that attempts to shoot where stuff will be, not where they are). To fetch data from other components, its really tricky to do so without a specific reference...

Lastly for this part, a very simple AI movement code for the enemy ships.

using UnityEngine;
using System.Collections;



/// <summary>
/// Simple AI movement behavior that attempts to circle around a target
/// at max speed. Please note that turn speed and max speed will affect
/// the result of this AI, as the movement attributes might or might not
/// make the desired movement possible
/// </summary>
[AddComponentMenu("Scripts/Movement/AI/AI Movement Circle Target Behavior")]
public class AIMovCircleTargetBehavior : MonoBehaviour, ITargeteer {
    public GameObject objectToMove;
    public SendMessageOptions messagesOptions;

    public float minDesiredDistance;
    public float maxDesiredDistance;

    [Tooltip("Position 'in circle' that the AI attempts to reach. " +
     "Different values might result in interesting behaviors")]
    public float angleLookAhead;
    
    private TargetableBehavior m_CurrentTarget;
    private Transform m_TargetTransform; // Small optimization.

    // Small optimization
    private Transform m_Transform;



    private void Awake() {
        m_Transform = this.transform;
    }



    private void Update() {
        if(m_CurrentTarget != null && objectToMove != null) {
            Vector2 dist = (Vector2) (m_Transform.position - m_TargetTransform.position);
            float angleFromTarget = Mathf.Atan2(dist.y, dist.x) * Mathf.Rad2Deg;
            float desiredDistance = Mathf.Clamp(dist.magnitude, minDesiredDistance, maxDesiredDistance);

            angleFromTarget += angleLookAhead;

            Vector2 desiredPosition = (Vector2) m_TargetTransform.position;
            desiredPosition.x += Mathf.Cos(angleFromTarget * Mathf.Deg2Rad) * desiredDistance;
            desiredPosition.y += Mathf.Sin(angleFromTarget * Mathf.Deg2Rad) * desiredDistance;

            // We want to move to the desired position
            Vector2 moveDist = desiredPosition - (Vector2) m_Transform.position;
            float moveAngle = Mathf.Atan2(moveDist.y, moveDist.x) * Mathf.Rad2Deg;

            objectToMove.SendMessage(MovMsgs.TurnToAngle, moveAngle, messagesOptions);
            objectToMove.SendMessage(MovMsgs.Accelerate, 1.0f, messagesOptions);
        }
    }



    public void SetTarget(TargetableBehavior target) {
        m_CurrentTarget = target;
        if(target != null) {
            m_TargetTransform = target.transform;
        } else {
            m_TargetTransform = null;
        }
    }
}


Now isn't this a plain simple AI? It is, indeed! It will wait for a SetTarget message to define a target object related to the AI movement – right now, it can only be the player. Then it will calculate the current angle and distance from this object to the target, and calculate a desired destination by offsetting a bit the angle either clockwise or counter-clockwise, and defining a min and max distance from the target. The movement itself is a problem to the movement behavior being used!

This AI is so minimalist and simple that the values for an interesting behavior is kind of like finding magic values by trial and error. Try it, and you'll find a few interesting patterns. A little more coding and I could have something boids-like, with emergent behavior. When planning for AI, you can often first try simple things and see how they behave in groups. Emergent “complex” behavior is always interesting.

Enough coding for this part! I did some quick artwork for the ships to make the testing more pleasant. Since now I have a movement behavior that rotates the objects, I need to restructure the player and enemies prefabs. I'll not get in details here, as this is very simple – but you can download the code for this part if you want to see how I did.

In this part I've introduced a way to use SendMessage in a less error prone way. By using C# interfaces and constant strings, I was able to drop the main reasons that I hated SendMessage and refused to use Component Orientation widely, attempting to use Objects Orientation in all possible situations. I'll say again. OOP is not bad. It's not evil. But in a components system, it makes it a lot easier to not choose optimal choices. Both paradigms can work together, but the programmer must not be biased to either one of them, and use the best tool that solves his problems!

So far, the components do still feel kind of meaningless. In the next part, the cannons will be able to shoot some placeholder bullets, concluding the basic framework for the game. From there, in part four and onwards, it's all about making some new behaviors to the existing systems.

The project for this part can be downloaded here.

sábado, 1 de novembro de 2014

Unity and Components Orientation Tutorial - Part Two

Infinite Sail part two: the turrets.

In part 1 of this series, I've introduced what it is about: trying to convince you that Component Orientation is a good way to go within Unity, and helping newcomers to think in component terms. And for this, I would make a game called Infinite Sail. In this part, I'll finally get some code! Please, do remember that I will consider that you already know the basics of the Unity and C#, as this is not a really “new programmers” tutorial series. Even though I don't think things will get so complex, I won't go on details about some things, and trust that you'll google or research when needed.

Sooooo... Let's get started! First, I created a new project in Unity, set it to default as 2D (I never used 2D collision system, so I'll adventure on this). Created the basic folders structures that I'm used to work, set up my Unity layout, and finally created a new empty scene.

I've set the background color to an ocean blue, the camera depth to 1000 and position z = -500, and the orthographic size to 100. In the end, I got something like this:


As in every new project, I have no idea on where to start coding... even more on a tutorial. To make matters worse, I have no idea of what this game will be, either! Since it's a tutorial and the game does not to be fun, let's go with something simple. Enlightenment Age ships sailing on a waveless ocean shooting each other with its cannons. Or shooting monsters... Let's stay with ships, for now...

I have a problem when starting new projects, that's my inability to make something playable right from the start, so it might be a while until we have “a game”. For this game, I want that a group of cannons can pick a target by themselves and start shooting without the player needing to issue an order. I can change this later. So for now, I'll do the initial automatic aiming system.

For this, I'll need one enumerator and a few components:

  • TargetTypes: PlayerShip or EnemyShip;
  • TargetableBehavior: means that something can be aimed at;
  • TargetablesLister: to keep lists of our targets;
  • TargetFetcherBehavior: fetches a target for shooting;
  • TargeteerFullRotationBehavior: aims at a target.

That's enough for part two, I guess! When I had my first contact with Components Orientation, I had the feeling that I needed to do much more code than needed to fulfill a task. Actually, this is often true! But there is a benefit that outweighs this cost really fast, as your projects grows more and more complex. It's low coupling between components and game objects. If you've already developed a game in OOP, you'll feel that later, this extra code now will have saved you from hundreds, if not thousands, of lines of code created or changed.

The aiming system will work in a very simple fashion. Any GameObject with a TargetableBehavior component is supposed to be a target that can be aimed at. Don't care what the GameObject is. It could be a barrel, an alien, the player, enemies, an immortal rock - anything. Components like TargetFetcherBehavior will search for appropriate TargetableBehavior instances and set them as a target. Then components like TargeteerFullRotationBehavior will try to aim at them.

Enough talking! First, we do the TargetTypes enumerator. Its fast!

// Types that a TargetableBehavior can be. Values are defined to avoid
// elements reordering to affect the Prefabs. It is ugly, but better safe
// than bug, in this case...
//
// As a game grows, we might reorder the enum to group elements and make
// it easier to know what exists and what does not. In this case is
// not likely to happen but again, better safe than bug
//
// Last Value: 2
public enum TargetTypes {
    None = 0,

    PlayerShip = 1,
    EnemyShip = 2
}


Next, comes the TargetableBehavior, and it's related behavior, the TargetablesLister. The “lister”, in this case, is here only to make it easier to find instances of TargetableBehavior. I didn't call it “manager” because, in reality, it doesn't manage anything. For us, it will be better that way, since TargetableBehavior is not a key component for prefabs or objects pooling.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;



/// <summary>
/// Keeps lists of TargetableBehavior. The behaviors themvelves are
/// responsible in keeping the lists updated. This Lister only
/// stores the lists.
/// </summary>
public class TargetablesLister : MonoBehaviour {
    // We want this to be a "singleton".
    static private TargetablesLister m_Instance;
    static public TargetablesLister Instance { get { return(m_Instance); } }

    // Lists for storing instances references. Could be a dictionary,
    // but I'll keep as separates variables for now
    private List<TargetableBehavior> m_PlayerShips;
    private List<TargetableBehavior> m_EnemyShips;



    private void Awake() {
        // "Singleton" setup
        if(m_Instance == null) {
            m_Instance = FindObjectOfType<TargetablesLister>();
        }
        if(m_Instance != this) {
            Debug.LogWarning(
                "Multiples instances of TargetableLister detected. Deleting this component instance.",
                this.gameObject
            );
            TargetablesLister.Destroy(this);
            return;
        }

        // Allocate the lists
        m_PlayerShips = new List<TargetableBehavior>();
        m_EnemyShips = new List<TargetableBehavior>();
    }



    /// <summary>
    /// Returns a list of targets by type.
    /// </summary>
    public List<TargetableBehavior> GetTargets(TargetTypes targetType) {
        List<TargetableBehavior> targetsList = null;
        
        switch(targetType) {
        case TargetTypes.EnemyShip:  targetsList = m_EnemyShips;  break;
        case TargetTypes.PlayerShip: targetsList = m_PlayerShips; break;
        }

        return(targetsList);
    }



    /// <summary>
    /// (Unsafe) Registers a new target in the lists.
    /// </summary>
    public void Register(TargetableBehavior target, TargetTypes targetType) {
        List<TargetableBehavior> targetList = GetTargets(targetType);
        ValidateRegisteredStatus(false, target, targetList);
        targetList.Add(target);
    }



    /// <summary>
    /// (Unsafe) Unregister a given target from the lists
    /// </summary>
    public void Unregister(TargetableBehavior target, TargetTypes targetType) {
        List<TargetableBehavior> targetList = GetTargets(targetType);
        ValidateRegisteredStatus(true, target, targetList);
        targetList.Remove(target);
    }


    

    #region Debug Methods

    /// <summary>
    /// Validates if a target is registered in a given list or not.
    /// Won't fix anything, but will emit errors if possible.
    /// </summary>
    [
       System.Diagnostics.Conditional("DEBUG_GAME"), 
       System.Diagnostics.Conditional("UNITY_EDITOR")
    ]
    private void ValidateRegisteredStatus(
        bool correctStatus, TargetableBehavior target, List<TargetableBehavior> list
    ) {
        if(target == null) {
            Debug.LogError("[Error] Registered validation failed. Target is null");
            return;
        }

        if(list == null) {
            Debug.LogError("[Error] Received null list!");
            return;
        }

        bool registered = list.Contains(target);
        if(registered != correctStatus) {
            Debug.LogError(
                "[Error] Registered validation failed. Expected [" + correctStatus +
                "] and found [" + registered + "].",
                target.gameObject
            );
        }
    }

    #endregion
}


As you can see, the code is very simple. New C# programmers might learn two new things in this code. The first one, is the Summary comments before the methods and class. These comments will appear on the code completion screen, and this can be quite helpful as the project grows with more and more lines of code, and we forget what “old” methods did.


The second one is the System.Diagnostics.Conditional. Conditional methods are normal methods with some limitation as in that they cannot return values. They are tied to compiling directives, as macros defined with #define. Conditional Methods will always be compiled, but if the needed directives are removed, the compiler will remove the calls for those methods as well. This is a clean way of calling methods to validate the game (in order to detect bugs) and easily remove these validations calls for the release build, and thus making the release build slightly faster.

In the code, I made a method to validate if a given reference exists or not within a list. This method will be called as long as either UNITY_EDITOR or DEBUG_GAME macros are defined. UNITY_EDITOR is defined by default while on the editor. DEBUG_GAME I have to define it myself, in case I want these validation methods to be called in stand alone builds of my game. You can find a lot more on Conditionals on google and in the MSDN C# Reference.

The TarbetablesLister component is supposed to behave like a “singleton”. The quotes are here because the singleton is actually a component. If you're new to the Singleton concept (in or out of Unity context), I'd advise some research on it. It's very simple but interesting pattern. The remaining of the class code is pretty straightforward, and I believe it doesn't need any explanation.

Next, comes the TargetableBehavior class. For now, this one will also be really, really simple.

using UnityEngine;
using System.Collections;



/// <summary>
/// Component used to flag that this object is a target.
/// </summary>
public class TargetableBehavior : MonoBehaviour {
    // Target type. Only change this with the correct method
    [Tooltip("Sets the target type to be used at OnEnable. Don't change on inspector at runtime!")]
    [SerializeField] protected TargetTypes m_CurrentType;



    /// <summary>
    /// Current type of this target. At runtime, it is advised to change its
    /// type by using this property.
    /// </summary>
    public TargetTypes CurrentType {
        set {
            if(this.gameObject.activeSelf && this.gameObject.activeInHierarchy) {
                if(m_CurrentType != value) {
                    Unregister();
                    m_CurrentType = value;
                    Register();
                }
            } else {
                m_CurrentType = value;
            }
        }
        get {
            return(m_CurrentType);
        }
    }



    private void OnEnable() {
        Register();
    }



    private void OnDisable() {
        Unregister();
    }



    // Methods for registering on lister
    private void Register() {
        TargetablesLister.Instance.Register(this, m_CurrentType);
    }

    private void Unregister() {
        TargetablesLister.Instance.Unregister(this, m_CurrentType);
    }
}


All that the TargetableBehavior component does is to register itself in the lister when enabled, and unregister when disabled. Beginners to Unity might notice I made a protected member variable, m_CurrentType with the SerializeField attribute. I made the member variable non-public so I, or any other programmer, will not attempt to change it directly on code. This might avoid some bugs in the far future. However, I still want to be able to set it up on Inspector and save its value in prefabs or scene objects. Public variables are serialized and set to appear on Inspector by default, but I need the SerializeField attribute to serialize a protected or private member.

There's a nice property to change the type at runtime. I can't guarantee it is bug proof right now.

Now, to tie up our “automatic targeting system”, we need the TargetFetcherBehavior. This behavior will be stupid and inefficient. Why? Because I am making it solely for testing purposes. I want that different objects have the ability to own a target fetching algorithm that matches its behavior. But I don't have any special object right now, so it doesn't make sense to go around making customized targeting algorithms yet...

using UnityEngine;
using System.Collections;
using System.Collections.Generic;



/// <summary>
/// Dummy component that will be removed later. Here for testing
/// purposes.
/// </summary>
public class TargetFetcherBehavior : MonoBehaviour {
    // Message sent when a target is fetched
    public const string MsgSetTarget = "SetTarget";

    // Message target and options. We don't really need the option here
    // (could use DontRequireReceiver). But lets give this decision power
    // to our designers!
    public GameObject targetFetchedReceiver;
    public SendMessageOptions messageOptions;

    // Current type of this targeter
    public TargetTypes currentType;



    private void Update() {
        List<TargetableBehavior> targetsList = null;
        switch(currentType) {
        case TargetTypes.EnemyShip: 
            targetsList = TargetablesLister.Instance.GetTargets(TargetTypes.PlayerShip); break;
        case TargetTypes.PlayerShip:
            targetsList = TargetablesLister.Instance.GetTargets(TargetTypes.EnemyShip); break;
        }

        if(targetsList != null) {
            // We will simply search for the closest target
            TargetableBehavior currentTarget = null;
            float smallestSqrDist = float.PositiveInfinity;
            Vector3 pos = transform.position; // Small costless optimization

            for(int i = 0; i < targetsList.Count; ++i) {
                Vector2 dist = (Vector2) (pos - targetsList[i].transform.position);
                float sqrDist = dist.sqrMagnitude;
                if(sqrDist < smallestSqrDist) {
                    smallestSqrDist = sqrDist;
                    currentTarget = targetsList[i];
                }
            }

            // Send the target message. Yes, we will set a new target (even
            // if the same) every frame. This could be better, I know, but
            // this is a dummy class that most likely will be deleted later.
            // Or, at least, rewritten. Let me save some effort right now.
            if(currentTarget != null && targetFetchedReceiver != null) {
                targetFetchedReceiver.SendMessage(MsgSetTarget, currentTarget, messageOptions);
            }
        }
    }
}


The TargetFetcherBehavior simply searches for the nearest target available. Every frame. Don't place too many of them in the scene! Anyway, you can see that I added a constant string called MsgSetTarget. My constants starting with “Msg” means that they are messages used with SendMessage or BroadcastMessage. There're a few good things about making them a “named” constant instead of “magic values”, but the most important here is to make it clear to the coder that this object send this message.

Component Orientation works better when the component does not know about other components. The less each component needs to know about others, easier it is to reuse components on different objects. The problem then is how to call methods of other components, that you don't know what they are, or even if they exist? For solving this problem, Unity give us the SendMessage and BroadcastMessage methods. They are a blade that cut to either side, though. Any typo on the message or the receiving method name, and the message won't get through. When a component receives a message, it is very hard to know WHICH GameObject and component sent it – you'll need to use a stack trace and the Debug.Log ability of “flagging” objects (second argument). The MonoDevelop “FindReferences” won't help much, either.

You'll need to come up with small tricks and coding practices to reduce these errors. Things like SendMessageOptions.RequireReceiver for testing behaviors, making messages on code as named constants, and so on. Just like we need to know many “defensive” measures when programming inheritance and design patterns, we will also need to learn quite a few for components orientation.

Now, finally, our targeteer to aim at stuff!

using UnityEngine;
using System.Collections;



/// <summary>
/// Rotates the shortest distance to aim at a given target.
/// </summary>
public class TargeteerFullRotationBehavior : MonoBehaviour {
    // Related messages:
    // MsgSetTarget (in)

    // Targeting
    private TargetableBehavior m_CurrentTarget;
    private Transform m_CurrentTargetTransform; // Small optimization

    // Rotation settings
    [Tooltip("Speed of rotation, in degrees per second")]
    public float rotationSpeed;
    private float m_CurrentAngle;

    // Small optimization
    private Transform m_Transform;



    private void Awake() {
        m_Transform = this.transform;
        m_CurrentAngle = 0.0f;
    }



    private void Update() {
        // Aim at the target
        if(m_CurrentTarget != null) {
            Vector3 distance = m_CurrentTargetTransform.position - m_Transform.position;
            float targetAngle = Mathf.Atan2(distance.y, distance.x) * Mathf.Rad2Deg;
            m_CurrentAngle = Mathf.MoveTowardsAngle(
                m_CurrentAngle, targetAngle, rotationSpeed * Time.deltaTime
            );

            Vector3 currentRotation = m_Transform.eulerAngles;
            currentRotation.z = m_CurrentAngle;
            m_Transform.eulerAngles = currentRotation;
        } else {
            // Simple cleanup
            m_CurrentTargetTransform = null;
        }
    }



    /// <summary>
    /// Sets a target for this targeteer.
    /// [Possible Message]
    /// </summary>
    public void SetTarget(TargetableBehavior newTarget) {
        m_CurrentTarget = newTarget;
        m_CurrentTargetTransform = newTarget.transform;
    }
}


Very simple, right? This component receives a target with a SetTarget message sent by a target fetcher, and then takes the shortest turning angle to aim towards it. You'll see that I cached the targeteer owner transform, and the transform of the target as well. Every time you use “transform” (equivalent of this.transform, or somecomponent.transform), Unity will actually retrieve it as a GetComponent every time. So I just cached them since this is a very simple optimization. Lastly, if you look at the SetTarget method, I “flagged” it as “Possible Message”, so that I think it twice before renaming the method, or changing the parameters.

You can now place a few objects in the scene and move around to see their behavior.


This part got long already, so I'll do no more coding for now. As you can see, we created a simple targeting system that can work on its own. In the next part, we'll make some moving code for the player, as well as for the enemy ships. Again, we will try to make it component based. It will feel like we're writing more code than needed, but soon it will start to be worth it. Meanwhile, you can go on drawing a few nice sprites, heh? Till then!

The source of part two can be downloaded here.

Unity and Components Orientation Tutorial - Part One

Hello!

Ever since I've started to develop games on Unity, I've come across many tutorials here and there. Since I was already a somewhat experienced programmer by the time, I quickly outgrew them and started to use the manual and reference guide. After a couple of years, I knew a lot about Unity, but I lacked the knowledge to use its full power. And that's what this tutorial is about!

I've learned to develop on Unity by thinking as an Object Oriented programmer. The tutorials showed me how to do this. People around taught me how to do this. There was a glimpse of another paradigm that quickly I learned to recognize as “the round-about, bad style way of accomplishing something”. That paradigm was called Component Orientation.

Now, don't get me wrong. Object Orientation is not bad, but you CAN'T put Unity at its best if you don't use Component Orientation (Unity is a Component Based System, after all). And what's most troublesome, is that Objects Orientation and Components Orientation often does not get along well. If you're solving a problem with inheritance, for example, chances are that you're definitely wasting components potential!

So, I've decided to make this short series of tutorials in which I'll develop a top-down shooter game called Infinite Sail. In this series, I'll try to to convince you why Component Orientation is a good way to go, and how to think in this paradigm. However, I'm not a professional and even less a master in this paradigm myself, so I might not be able to use its full power.

Finally, I'll assume you already know how to program C# and the basics of Unity. I don't think things will get so complex, but I might omit things that I think that are too obvious, and you might need to research on them yourself if you're a newcomer (i.e Mathf.Atan2).

To keep things organized (in the blog), I'll finish this part here, and start the coding in the next post. See you there!