devNotes 6-27-16 mvc structure ui interaction

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;

#if UNITY_5_3_OR_NEWER
using UnityEngine.SceneManagement;
#endif


namespace thelab.mvc
{
    /// <summary>
    /// Extension of the BaseApplication class to handle different types of Model View Controllers.
    /// </summary>
    /// <typeparam name="M"></typeparam>
    /// <typeparam name="V"></typeparam>
    /// <typeparam name="C"></typeparam>
    public class BaseApplication<M, V, C> : BaseApplication
        where M : Element
        where V : Element
        where C : Element
    {
        /// <summary>
        /// Model reference using the new type.
        /// </summary>
        new public M model { get { return (M)(object)base.model; } }

        /// <summary>
        /// View reference using the new type.
        /// </summary>
        new public V view { get { return (V)(object)base.view; } }

        /// <summary>
        /// Controller reference using the new type.
        /// </summary>
        new public C controller { get { return (C)(object)base.controller; } }
    }

    /// <summary>
    /// Root class for the scene's scripts.
    /// </summary>
    public class BaseApplication : Element
    {
        /// <summary>
        /// Arguments to be passed between scenes.
        /// </summary>        
        static List<string> __args;

        /// <summary>
        /// Flag that indicates the first scene was loaded.
        /// </summary>
        static bool m_first_scene;

        /// <summary>
        /// Little static init.
        /// </summary>
        static BaseApplication() { m_first_scene = true; }

        /// <summary>
        /// Arguments passed between scenes.
        /// </summary>
        public List<string> args { get { return __args==null ? (new List<string>()) : __args; } }

        /// <summary>
        /// Verbose Level.
        /// </summary>
        public int verbose;

        /// <summary>
        /// Fetches the root Model instance.
        /// </summary>
        public Model model { get { return m_model = Assert<Model>(m_model); } }
        private Model m_model;

        /// <summary>
        /// Fetches the root View instance.
        /// </summary>
        public View view { get { return m_view = Assert<View>(m_view); } }
        private View m_view;

        /// <summary>
        /// Fetches the root Controller instance.
        /// </summary>
        public Controller controller { get { return m_controller = Assert<Controller>(m_controller); } }
        private Controller m_controller;

        /// <summary>
        /// Wrapper for the current scene's id.
        /// </summary>
        public int levelId {
            get {
                #if UNITY_5_3_OR_NEWER
                return SceneManager.GetActiveScene().buildIndex;
                #else
                return Application.loadedLevel;
                #endif
            }
        }

        /// <summary>
        /// Wrapper for the current scene's name.
        /// </summary>
        public string levelName {
            get {
                #if UNITY_5_3_OR_NEWER
                return SceneManager.GetActiveScene().name;
                #else
                return Application.loadedLevelName;
                #endif
            }
        }

        /// <summary>
        /// Async data structures.
        /// </summary>
        private List<UnityEngine.AsyncOperation> m_async_loads { get { return __async_loads == null ? (__async_loads = new List<UnityEngine.AsyncOperation>()) : __async_loads; } }
        private List<UnityEngine.AsyncOperation> __async_loads;

        private List<string> m_async_args { get { return __async_args == null ? (__async_args = new List<string>()) : __async_args; } }
        private List<string> __async_args;

        /// <summary>
        /// Initialization.
        /// </summary>
        virtual protected void Start() {
            __async_loads = new List<UnityEngine.AsyncOperation>();
            __async_args = new List<string>();
            if (m_first_scene) { m_first_scene = false; OnLevelWasLoaded(levelId); }
            Notify("scene.start", new object[] { levelName, levelId });
        }

        /// <summary>
        /// Capture the level loaded event and notify controllers for 'starting' purposes.
        /// </summary>
        /// <param name="p_level"></param>
        private void OnLevelWasLoaded(int p_level) {
            Notify("scene.load", new object[] { levelName, levelId });
        }

        /// <summary>
        /// Notifies all application's controllers informing who's the 'target' and passing some 'data'.
        /// </summary>
        /// <param name="p_event"></param>
        /// <param name="p_target"></param>
        /// <param name="p_data"></param>
        public void Notify(string p_event, Object p_target, params object[] p_data) {                        
            Log(p_event + " [" + p_target + "]", 6);
            Traverse(delegate(Transform it) {
                Controller[] list = it.GetComponents<Controller>();
                for (int i = 0; i < list.Length; i++) list[i].OnNotification(p_event, p_target, p_data);
                return true;
            });
        }

        /// <summary>
        /// Notifies all application's controllers informing who's the 'target'.
        /// </summary>
        /// <param name="p_event"></param>
        /// <param name="p_target"></param>
        public void Notify(string p_event, Object p_target) { Notify(p_event, p_target,new object[]{}); }

        /// <summary>
        /// Notifies all application's controllers informing who's the 'target' after 'delay' in seconds and passing some 'data'.
        /// </summary>
        /// <param name="p_event"></param>
        /// <param name="p_target"></param>
        /// <param name="p_data"></param>
        public void Notify(float p_delay,string p_event, Object p_target,params object[] p_data) {            
            StartCoroutine(TimedNotify(p_delay,p_event,p_target,p_data));
        }

        /// <summary>
        /// Internal Notify to help timed notifications.
        /// </summary>
        /// <param name="p_delay"></param>
        /// <param name="p_event"></param>
        /// <param name="p_target"></param>
        /// <param name="p_data"></param>
        /// <returns></returns>
        private IEnumerator TimedNotify(float p_delay, string p_event, Object p_target,params object[] p_data) {
            yield return new WaitForSeconds(p_delay);
            Notify(p_event, p_target, p_data);
        }
        
        /// <summary>
        /// Adds a new scene by name. An async flag can control the load type.
        /// </summary>
        /// <param name="p_name"></param>
        /// <param name="p_async"></param>
        /// <param name="p_args"></param>
        public void SceneAdd(string p_name, bool p_async, params string[] p_args) {
            if (p_async) { StartCoroutine(SceneLoadAsync(p_name, true, p_args)); }
            else {
                __args = new List<string>(p_args);
                #if UNITY_5_3_OR_NEWER
                SceneManager.LoadScene(p_name, LoadSceneMode.Additive);
                #else
                Application.LoadLevelAdditive(p_name);
                #endif
            }
        }

        /// <summary>
        /// Adds a new scene.
        /// </summary>
        /// <param name="p_name"></param>
        /// <param name="p_args"></param>
        public void SceneAdd(string p_name,params string[] p_args) { SceneAdd(p_name, false, p_args); }

        /// <summary>
        /// Loads a new scene by name. A flag indicating if the load must be async can be informed.
        /// </summary>
        /// <param name="p_name"></param>
        /// <param name="p_async"></param>
        /// <param name="p_args"></param>
        public void SceneLoad(string p_name,bool p_async,params string[] p_args) {
            if (p_async) { StartCoroutine(SceneLoadAsync(p_name,false,p_args)); }
            else {
                __args = new List<string>(p_args);
                #if UNITY_5_3_OR_NEWER
                SceneManager.LoadScene(p_name, LoadSceneMode.Single);
                #else
                Application.LoadLevel(p_name);
                #endif
            }
        }

        /// <summary>
        /// Loads a new scene by name.
        /// </summary>
        /// <param name="p_name"></param>
        /// <param name="p_args"></param>
        public void SceneLoad(string p_name,params string[] p_args) { SceneLoad(p_name, false, p_args); }

        /// <summary>
        /// Internal method for async load level.
        /// </summary>
        /// <param name="p_name"></param>
        /// <param name="p_args"></param>
        /// <returns></returns>
        private IEnumerator SceneLoadAsync(string p_name,bool p_additive,params string[] p_args) {
            //float p = 0f;
            UnityEngine.AsyncOperation async = null;
            string ev = "";
            
            if(p_additive) {
                ev = "scene.add.progress";
                #if UNITY_5_3_OR_NEWER
                async = SceneManager.LoadSceneAsync(p_name,LoadSceneMode.Additive);
                #else
                async = Application.LoadLevelAdditiveAsync(p_name);
                #endif
            }
            else {
                ev = "scene.load.progress";
                #if UNITY_5_3_OR_NEWER
                async = SceneManager.LoadSceneAsync(p_name,LoadSceneMode.Single);
                #else
                async = Application.LoadLevelAsync(p_name);
                #endif                
            }

            m_async_loads.Add(async);
            m_async_args.Add(p_name + "~" + ev);

            yield return async;
            __args = new List<string>(p_args);
        }

        /// <summary>
        /// Update some internal states.
        /// </summary>
        void Update() {
            for(int i=0;i<m_async_loads.Count;i++) {
                
                UnityEngine.AsyncOperation async = m_async_loads[i];

                if (async != null) {
                    string args = m_async_args[i];
                    string s_name = args.Split('~')[0];
                    string s_ev = args.Split('~')[1];
                    if (s_ev != "") Notify(s_ev, new object[] { s_name, async.progress });
                    if (async.progress >= 1.0) m_async_loads[i] = null;                    
                }
                else {
                    m_async_loads.RemoveAt(i--);
                    m_async_args.RemoveAt(i--);
                }
            }
        }
    }
}

#pragma warning restore 0618

SHIM

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


namespace thelab.mvc
{
    /// <summary>
    /// Extension of the element class to handle different BaseApplication types.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Element<T> : Element where T : BaseApplication {
        /// <summary>
        /// Returns app as a custom 'T' type.
        /// </summary>
        new public T app { get { return (T)base.app; } }
    }

    /// <summary>
    /// Base class for all MVC related classes.
    /// </summary>
    public class Element : MonoBehaviour {

        /// <summary>
        /// Reference to the root application of the scene.
        /// </summary>
        public BaseApplication app { get { return m_app = Assert<BaseApplication>(m_app, true); } }
        private BaseApplication m_app;

        /// <summary>
        /// Reference to the variable storage.
        /// </summary>
        private Dictionary<string, object> m_store { get { return _store == null ? (_store = new Dictionary<string, object>()) : _store; } }
        private Dictionary<string, object> _store;

        /// <summary>
        /// Finds a instance of 'T' if 'var' is null. Returns 'var' otherwise.
        /// If 'global' is 'true' searches in all scope, otherwise, searches in childrens.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_var"></param>
        /// <param name="p_global"></param>
        /// <returns></returns>
        public T Assert<T>(T p_var, bool p_global=false) where T : Object {
            return p_var == null ? (p_global ? GameObject.FindObjectOfType<T>() : transform.GetComponentInChildren<T>()) : p_var;            
        }

        /// <summary>
        /// Finds a instance of 'T' if the named 'var' is null. Returns the storage['var'] otherwise.
        /// If 'global' is 'true' searches in all scope, otherwise, searches in childrens.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_key"></param>
        /// <param name="p_global"></param>
        /// <returns></returns>
        public T Assert<T>(string p_key, bool p_global = false) where T : Object {
            if (m_store.ContainsKey(p_key)) { return (T)(object)m_store[p_key]; }
            T v = (p_global ? GameObject.FindObjectOfType<T>() : transform.GetComponentInChildren<T>());
            m_store[p_key] = v;
            return v;
        }

        /// <summary>
        /// Finds a instance of 'T' locally if the named 'var' is null. Returns storage['var'] otherwise.        
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_key"></param>
        /// <returns></returns>
        public T AssertLocal<T>(string p_key) where T : Object {
            if (m_store.ContainsKey(p_key)) { return (T)(object)m_store[p_key]; }
            T v = GetComponent<T>();
            m_store[p_key] = v;
            return v;
        }

        /// <summary>
        /// Finds a instance of 'T' locally if 'var' is null. Returns 'var' otherwise.        
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_var"></param>
        /// <returns></returns>
        public T AssertLocal<T>(T p_var,string p_store="") where T : Object {
            T v = default(T);
            if (p_store != "") if (m_store.ContainsKey(p_store)) { return (T)(object)m_store[p_store]; }   
            v = p_var == null ? (p_var = GetComponent<T>()) : p_var;
            if (p_store != "") m_store[p_store] = v;
            return v;
        }

        /// <summary>
        /// Finds a instance of 'T' in this element's parent if the named 'var' is null. Returns storage['var'] otherwise.        
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_key"></param>
        /// <returns></returns>
        public T AssertParent<T>(string p_key) where T : Object {
            if (m_store.ContainsKey(p_key)) { return (T)(object)m_store[p_key]; }
            T v = GetComponentInParent<T>();
            m_store[p_key] = v;
            return v;
        }

        /// <summary>
        /// Finds a instance of 'T' in this element's parent if 'var' is null. Returns 'var' otherwise.        
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_var"></param>
        /// <returns></returns>
        public T AssertParent<T>(T p_var, string p_store = "") where T : Object {
            T v = default(T);
            if (p_store != "") if (m_store.ContainsKey(p_store)) { return (T)(object)m_store[p_store]; }
            v = p_var == null ? (p_var = GetComponentInParent<T>()) : p_var;
            if (p_store != "") m_store[p_store] = v;
            return v;
        }

        /// <summary>
        /// Retrieves a value from cache or store it.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_store"></param>
        /// <param name="p_value"></param>
        /// <returns></returns>
        public T AssertCache<T>(string p_store,T p_value) {
            if (m_store.ContainsKey(p_store)) { return (T)(object)m_store[p_store]; }
            m_store[p_store] = p_value;
            return p_value;
        }

        /// <summary>
        /// Helper method for casting.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T Cast<T>() { return (T)(object)this; }

        /// <summary>
        /// Searchs for a given element in the dot separated path.
        /// </summary>
        /// <param name="p_path"></param>
        /// <returns></returns>
        public T Find<T>(string p_path) where T : Component {
            List<string> tks = new List<string>(p_path.Split('.'));
            if (tks.Count <= 0) return default(T);
            Transform it = transform;
            while (tks.Count > 0) {
                string p = tks[0];
                tks.RemoveAt(0);
                it = it.FindChild(p);
                if (it == null) return default(T);
            }
            return it.GetComponent<T>();
        }

        /// <summary>
        /// Navigates the path and finds the first component.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_path"></param>
        /// <returns></returns>
        public T AssertFind<T>(string p_path) where T : Component {
            if (m_store.ContainsKey(p_path)) { return (T)(object)m_store[p_path]; }
            T v = Find<T>(p_path);
            m_store[p_path] = v;
            return v;
        }

        /// <summary>
        /// Sends a notification to all controllers passing this instance as 'target' and some 'data'.
        /// </summary>
        /// <param name="p_event"></param>
        /// <param name="p_data"></param>
        public void Notify(string p_event,params object[] p_data) { app.Notify(p_event, this, p_data); }
        
        /// <summary>
        /// Sends a notification to all controllers, after 'delay', passing this instance as 'target' and some 'data'.
        /// </summary>
        /// <param name="p_delay"></param>
        /// <param name="p_event"></param>
        /// <param name="p_data"></param>
        public void Notify(float p_delay,string p_event,params object[] p_data) { app.Notify(p_delay,p_event, this, p_data); }

        /// <summary>
        /// Traverses this element's Transform hierarchy with DFS approach.
        /// </summary>
        /// <param name="p_callback"></param>
        public void Traverse(System.Predicate<Transform> p_callback) {
            OnTraverseStep(transform,p_callback);
        }

        /// <summary>
        /// Traverse one element then its children.
        /// </summary>
        /// <param name="p_target"></param>
        /// <param name="p_callback"></param>
        private void OnTraverseStep(Transform p_target,System.Predicate<Transform> p_callback) {
            if(p_target) if(!p_callback(p_target)) return;
            for(int i=0;i<p_target.childCount;i++) { OnTraverseStep(p_target.GetChild(i),p_callback); }
        }

        /// <summary>
        /// Logs a message using this element information.
        /// </summary>
        /// <param name="p_msg"></param>
        public void Log(object p_msg, int p_verbose = 0) {
            //Only outputs logs equal or bigger than the application 'verbose' level.
            if (p_verbose <= app.verbose) Debug.Log(GetType().Name + "> " + p_msg);
        }
    }
}

 

 

using UnityEngine;
using System.Collections;

namespace thelab.mvc
{

    /// <summary>
    /// Base class for all View with notifications.
    /// </summary>
    public class NotificationView : NotificationView<BaseApplication> { }

    /// <summary>
    /// Base class for all View with notification features.
    /// </summary>
    public class NotificationView<T> : View<T> where T : BaseApplication {

        /// <summary>
        /// Fixed notification. Can be empty.
        /// </summary>
        public string notification;

    }

}

 

 

using UnityEngine;
using System.Collections;

namespace thelab.mvc
{    
    /// <summary>
    /// Base class for collision related classes.
    /// </summary>
    public class TimerView : NotificationView {        
        /// <summary>
        /// Flag that indicates if the time-scale affects this Timer.
        /// </summary>
        public bool scale = true;

        /// <summary>
        /// Flag that indicates if this View is active.
        /// </summary>        
        public bool active = true;

        /// <summary>
        /// Duration of the timer.
        /// </summary>
        public float duration;

        /// <summary>
        /// Cycles before completion.
        /// </summary>
        public int count;

        /// <summary>
        /// Elapsed time.
        /// </summary>
        public float elapsed;

        /// <summary>
        /// Current step.
        /// </summary>
        public int step;

        /// <summary>
        /// Restarts the timer.
        /// </summary>
        public void Restart() {
            elapsed = 0f;
            step = 0;
        }

        /// <summary>
        /// Activates the Timer.
        /// </summary>
        public void Play() {
            active = true;
        }

        /// <summary>
        /// Stops the Timer and reset its values.
        /// </summary>
        public void Stop() {
            active = false;
            Restart();
        }

        /// <summary>
        /// Updates the timer logic.
        /// </summary>
        void Update() {
            if (!active) return;
            elapsed += scale ? Time.deltaTime : Time.unscaledDeltaTime;
            if(elapsed>=duration) {
                elapsed = 0f;
                Notify(notification + "@timer.step");
                step++;
                if(step>=count) {
                    Notify(notification + "@timer.complete");
                    active = false;
                }
            }
        }

    }
}

 

 

using UnityEngine;
using System.Collections;
using thelab.mvc;

/// <summary>
/// Root class for all views.
/// </summary>
public class BounceView : View<BounceApplication>
{

    /// <summary>
    /// Reference to the Ball view.
    /// </summary>
    public BallView ball { get { return m_ball = Assert<BallView>(m_ball); } }
    private BallView m_ball;

    /// <summary>
    /// Reference to the Ball view.
    /// </summary>
    public TimerView timer { get { return m_timer = Assert<TimerView>(m_timer); } }
    private TimerView m_timer;
}

 

 

using UnityEngine;
using System.Collections;
using thelab.mvc;

/// <summary>
/// Class that handles the application data.
/// </summary>
public class BounceModel : Model<BounceApplication>
{
    /// <summary>
    /// Bounce counter.
    /// </summary>
    public int bounces;

    /// <summary>
    /// Win condition.
    /// </summary>
    public int winCondition;
	
}

 

 

using UnityEngine;
using System.Collections;
using thelab.mvc;

public class BounceController : Controller<BounceApplication>
{
    
    /// <summary>
    /// Handle notifications from the application.
    /// </summary>
    /// <param name="p_event"></param>
    /// <param name="p_target"></param>
    /// <param name="p_data"></param>
    public override void OnNotification(string p_event, Object p_target, params object[] p_data)
    {
        switch(p_event)
        {
            case "scene.load":                
                Log("Scene [" + p_data[0] + "]["+p_data[1]+"] loaded");
                break;
            case "ball.hit":
                string who = (string)p_data[0];
                if(who=="ground")
                {
                    app.model.bounces++;
                    Log("Hit " + app.model.bounces);
                    if(app.model.bounces>=app.model.winCondition)
                    {
                        app.view.ball.enabled = false;
                        app.view.ball.GetComponent<Rigidbody>().isKinematic = true;
                        Notify("game.complete");
                    }
                }
                break;

            case "game.complete":
                Log("Victory!");
                app.view.timer.Play();
                break;

            case "mid.trigger.enter":
            {                    
                Log("Mid Fall Enter!");
                ColliderView c = (ColliderView)p_target;
                c.collider.enabled = false;
            }
            break;

            case "start.trigger.exit":
            {
                Log("Start Fall Exit!");
                ColliderView c = (ColliderView)p_target;
                c.collider.enabled = false;
            }
            break;

            case "start.trigger.stay":
            {
                Log("Start Fall Stay ["+Time.time+"]");
            }
            break;

            case "ping.timer.step":
            {
                TimerView t = (TimerView)p_target;
                Log("Ping " + t.step);
            }
            break;

            case "ping.timer.complete":
            {                
                Log("Ping Complete!");
            }
            break;
        }
    }
	
}

 

fsm_ant_brain_code