1
\$\begingroup\$

I've been plugging away at an RTS framework for a few weeks now and I've decided to try out a basic MVC design pattern for Unity based on This Article

Anyway I guess I'm just looking for opinions here. This project is starting to get pretty complicated and I'm hemming and hawing over the idea that I'm not designing correctly for Unity. Here's a sample of my engine:

public class Engine : MonoBehaviour {

[SerializeField]
private UI uiData;

[SerializeField]
private Level level;

private SelectionController selectionController;
private UiController uiController;

private TeamController teamController;

private EntityController entityController;
private EntityCreationController entityCreationController;

void Start () {
    LoadLevel ();
}

void LoadLevel () {
    level.mapSize = new Vector2 (20, 20);


    selectionController = new SelectionController (level);
    uiController = new   UiController (level);
    entityController = new EntityController (level);
    teamController = new TeamController (level);


    //Entity
    EventManager.AddListener<EntitySpawnedEvent>(OnEntitySpawned);
    EventManager.AddListener<WaypointSetEvent>(OnWaypointSet);

    //Selection
    EventManager.AddListener<SelectionEvent>(OnSelection);
    EventManager.AddListener<StartSelectBoxEvent>(OnStartSelectBox);
    EventManager.AddListener<DragSelectBoxEvent>(OnDragSelectBox);

    //Entity Stuff
}

//
//Input Manager
//
void Update () {
    if (Input.GetMouseButtonDown(0)) {
        selectionController.CreateBoxSelection();
    }

    if (Input.GetMouseButtonUp(0)) {
        selectionController.SelectEntities();
    }

    if (Input.GetMouseButtonDown (1)) {
        teamController.SetWaypoint ();
    }

    selectionController.DragBoxSelection();
}


//
//Event Recievers
//


private void OnStartSelectBox(StartSelectBoxEvent e) {
    uiController.StartSelectBox(e.anchor);
}

private void OnDragSelectBox(DragSelectBoxEvent e) {
    uiController .DragSelectBox(e.outer);
}

private void OnSelection(SelectionEvent e) {
    entityController.selection = e.selection;
    uiController.ClearBox();
}

private void OnWaypointSet (WaypointSetEvent e) {
    entityController.UpdateEntityWaypoints (selectionController.selection, e.waypoint);
}

private void OnEntitySpawned(EntitySpawnedEvent e) {

}

public void OnBaseBuilderButton (Transform baseBuilder) {
    uiController.ToggleBaseBuilder (baseBuilder);
}

public void OnPurchaseEntity (GameObject entity) {
    entityCreationController.CreateNew (entity);
}

public void OnCreateEntity (EntityCreationEvent e) {

}

}

Events are each classes that contain pertinent info about that event, ie...

public abstract class GameEvent {

};

public class TestEvent : GameEvent {
    public int number;

    public TestEvent (int n) {
        number = n;
    }
}

public class MasterPulseEvent : GameEvent {

}

public class SelectionEvent : GameEvent {

    public List<Entity> selection { get; private set; }

    public SelectionEvent(List<Entity> selection){
        this.selection = selection;
    }
}

Events are handled by a generic event controller programmed originally byt Ben D'Angelo (The guy with the blog). It's a really slick system. However, the design patterns are what I'm having trouble with.

Basically the Engine handles all input and output. Additionally, it handles all communications between the controllers. Controllers never speak with one another, but fire events which are then heard by the Engine object and directed to the appropriate controllers, views etc..

The Engine subscribes like this:

EventManager.AddListener<WaypointSetEvent>(OnWaypointSet);

Then calls a controller like this:

private void OnWaypointSet (WaypointSetEvent e) {
    entityController.UpdateEntityWaypoints (selectionController.selection, e.waypoint);
}

Here's an example of one of my controllers.

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

public class EntityController {
    public List<Entity> selection;
    public Level level { get; private set; }

    public EntityController (Level level) {
        this.level = level;
    }

    public void UpdateEntityWaypoints (List<Entity> selections, Vector2 waypoint) {
        Vector2[] formationPosition = GenerateFormation (selections.Count, waypoint);
        Vector2 currentWaypoint = waypoint;
        int entities = selections.Count;
        foreach (Entity e in selections) {
            e.Waypoint = formationPosition[selections.IndexOf(e)];
        }
    }

    public Vector2[] GenerateFormation(int numOfUnits, Vector2 waypoint) {
        Vector2[] formation = new Vector2[numOfUnits];
        int posX = 0;
        int posY = 0;
        int size = Mathf.RoundToInt (Mathf.Sqrt(numOfUnits));
        waypoint.x -= size / 2;
        waypoint.y += size / 2;
        for (int i = 0; i < formation.Length; i++) {
            formation [i] = new Vector2 (posX, posY) + waypoint;
            posX++;
            if (posX >= size) {
                posY--;
                posX = 0;
            }
        }
        return formation;
    }
}

So the order of execution is such like this:

  1. Engine takes input
  2. Engine directs to controller
  3. Controller fires events
  4. Engine listens and redirects

After reading the article I was determined to give this a go but I'm starting to realize that using the MVC design pattern between the engine and my controllers is becoming really verbose. This is because EVERYTHING has to pass through the engine. It feels like I'm coding double time.

Overall though, I'm very pleased with the stability of programming like this. I'm just wondering if I could get a second opinion with regards to this design pattern and unity.

Am I barking up the wrong tree with this design pattern or is there anything I'm missing? For instance, I feel that I'm going to need to call a Coroutine from within one of the controller classes but I can't because they aren't MonoBehaviours.

I suppose I'm just looking for opinions here. If it's terrible, that's acceptable.

\$\endgroup\$
  • 2
    \$\begingroup\$ If this solution is working for you, then it's fine to keep using it. If it's not working for you, it may be better to rephrase this question in terms of what problem you're having with it that you want to solve, with a minimal code example. If you're just looking for opinions/feedback about your approach, then the Code Review SE might be a better place to ask. Right now you've given us a lot of code to evaluate without clear criteria, which is more in their wheelhouse. GD.SE specializes more in questions with verifiable answers. \$\endgroup\$ – DMGregory Apr 14 '16 at 23:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.