Working with a lot of projects with a different architectures due to my proffesional experience i've faced diffenent problems in implementing some features, so i've decided to use single solid and simple architecture for all future games and applications, not only unity, but .NET too.
The main concept of Contexts architecrure based on several architecture patterns, there are:

  • Composite pattern - to make class tree clear an easy to use, also to utilize all related content to a single Context. Elements nesting provides control of objects relations and enough flexible to provide different types of architectures.
  • MVC pattern - to separate business logic from view and model. It makes workflow clear and allows to work with data, logic and view separately from each other.
  • KISS principle - to make code clear.
  • SRP principle - affects single Composite element which must be designed to do single job, otherwise they are must be dedicaded nested contexts.

The master element for such kind of architecture is Context. For familiar with Unit3D developers context tree will be similar to Transform's tree in Hierarchy view, another example is directory tree with single difference that any folder can implement some custom logic. Also every single context must be an MVC's controller, provide (or not) access to data (model) and optionaly has possibility to interact with view, if it possible - not directly. We can separate some requirements before implement this:

  1. Context has 0 or more child contexts.
  2. Context provides methods to add and remove contexts.
  3. Context provides access to child contexts.
  4. Context can operate it's children.
  5. Context know it's parent, but abstractly.
  6. Context destroys all children when it were destroyed.
  7. Context can interact with model directly if model were specified.
  8. Content must work with an inherited type.
    The result code will look similar to this:
    /// <summary>
    ///     Base context class to derive custom implementations for context trees from.
    /// </summary>
    public class Context : IDisposable
    {
        private readonly IList<Context> _children;

        #region Properties

        /// <summary>
        ///     Parent context of this context instance. Null for root context objects.
        /// </summary>
        public Context Parent { get; internal set; }

        /// <summary>
        ///     Root context for current context tree.
        /// </summary>
        public Context Root { get { return Parent == null ? this : Parent.Root ; } }

        /// <summary>
        ///     Indexer accessor for child contexts.
        /// </summary>
        /// <param name="index"> Index of child context. </param>
        /// <returns> Child context at given index. </returns>
        public Context this[int index]
        {
            get { return _children[index]; }
        }

        /// <summary>
        ///     Count of child contexts.
        /// </summary>
        public int ChildCount { get { return _children.Count; } }

        #endregion

        #region Constructors

        /// <summary>
        ///     Creates new context with given capacity.
        /// </summary>
        /// <param name="capacity"> Count of possibly attached children. Makes child addition a little faster. </param>
        public Context(int capacity = 8)
        {
            _children = new List<Context>(capacity);
        }

        #endregion

        #region Mehtods : Public

        /// <summary>
        ///     Check if given context is child of this.
        /// </summary>
        /// <param name="context"> Given context to check. </param>
        /// <returns> True if cref="</returns>
        public virtual bool ContainsChild(Context context)
        {
            return context != null && _children.Contains(context);
        }

        /// <summary>
        ///     Gets child context of given type.
        /// </summary>
        /// <typeparam name="T"> Search child type. </typeparam>
        /// <returns> Child context of given type or null. </returns>
        public virtual T GetChild<T>() where T : Context
        {
            for (var i = 0; i < _children.Count; i++)
                if (_children[i] is T) return _children[i] as T;
            return null;
        }

        /// <summary>
        ///     Gets child contexts collection of given type.
        /// </summary>
        /// <typeparam name="T"> Search child type. </typeparam>
        /// <returns> Collection of contexts of given type. Collection can be empty. </returns>
        public virtual IEnumerable<T> GetChilden<T>() where T : Context
        {
            return _children.OfType<T>();
        }

        /// <summary>
        ///     Adds context instance as child.
        /// </summary>
        /// <typeparam name="T"> Type of given context instance. </typeparam>
        /// <param name="context"> Context instance to add. </param>
        /// <returns> Recently added context instance. It will be returned even if it were not added cause it's already a child of current context. </returns>
        public virtual T AddChild<T>(T context) where T : Context
        {
            if (ContainsChild(context)) return context;
            context.Parent = this;
            _children.Add(context);
            return context;
        }

        /// <summary>
        ///     Removes context instance from this context.
        /// </summary>
        /// <typeparam name="T"> Type of given context instance. </typeparam>
        /// <param name="context"> Context instance to remove. </param>
        /// <returns> Recently removed context instance. It will be returned even if it were not removed cause it's already not a child of current context. </returns>
        public virtual T RemoveChild<T>(T context) where T : Context
        {
            if (ContainsChild(context))
            {
                _children.Remove(context);
                context.Parent = null;
            }
            return context;
        }
        
        /// <summary>
        ///     Removes all children from current context.
        /// </summary>
        public virtual void Clean()
        {
            for (int i = 0; i < _children.Count; i++)
            {
                RemoveChild(_children[i]).Dispose();
            }
        }

        #endregion

        #region Implicit interface implementation
        
        /// <summary>
        ///     Disposes current object. Also will call <see cref="Clean"/> method to clean children.
        /// </summary>
        public virtual void Dispose()
        {
            Clean();
            Parent.RemoveChild(this);
        }

        #endregion
    }

Now we can feel free to operate with a Context objects relatively to each other and make logic tree with completely branch clean up. Usage will look similar to this:

var a = new Context();
var b = a.AddChild(new Context());
var c = a.AddChild(new Context());
var d = b.AddChild(new Context());

// true
var check1 = a == b.Parent;
// true
var check2 = a[0] == d.Parent;

Keep in mind, that you can create children inside any context based on data, which can be passed in or not. Also all methods are suitable for override, so you can implement your own logic on any step of tree building and executing.

That's all for now, in future articles i'll explain how to work with data and inherited contexts.