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
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; } } }