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