Introduction to Model-View-Controller
Posted on March 7, 2017 by tkcmdr
Hey there,
There's no contesting that ROBLOX programmers often fail to implement program architectures that make their games easily scalable and maintainable. Frequently our first compulsion when scripting is to slap a script into whatever we intend to program. Doing this seems convenient at first, but it has a few inherent disadvantages:
- The script lives in the workspace and is therefore vulnerable to accidental deletion.
- It can be difficult to find as the game grows.
- You will often end up with lots of copies, each of which you will have to update when you want to make even a single-line change.
The longer a project goes on, the more these sloppy practices tend to bite you in the butt. You’ve got hundreds, potentially thousands, of lines of duplicate code that are neither interconnected nor easily serviceable. Preferably, your code should be more like a framework than a hodge-podge of batch scripts if you plan to make that murder clone you have in mind.
Allow me to introduce you to something called MVC.
MVC, or Model-View-Controller, was developed during the 1970s as a modular way to write graphical user interface software. It has three components that do distinct jobs; they are as follows:
Model: The code that defines the behavior and data of an object
View: The code that defines the user interface
Controller: The code that communicates actions to the model when something happens in the view, and vice versa
In the case of ROBLOX, you don't have to write the code for the GUI, because it's already been made for you: it's known as the workspace. It may seem a bit strange, then, to use MVC for making games, but bear with me.
Since the View has already been done for you, you are left to write Model and Controller.
What's the Model, exactly?
Allow me to answer that question with a question. Several questions, actually.
Have you ever used a Part
’s BrickColor
property to change its color, or its Touched
event to know when it has been touched by something? Maybe you’ve used a TextLabel
’s Text
property to get user input, and maybe you made that TextLabel
using Instance.new()
.
In all these cases, you were using a wonderful component of ROBLOX called the API, which is a collection of classes that you can use to make awesome games very easily. Classes are essentially blueprints for data structures that you can copy and use to make or represent other things: case in point, the Part
class. An instance (copy) of the part class represents itself in the Workspace as a block that can be used to build stuff. It has properties like Transparency
, Anchored
, Shape
, and more, as well as a bunch of methods and events that enable you to make all sorts of amazing games. You use them all the time, and they make game development on ROBLOX wonderfully intuitive and efficient.
Again, the model is essentially the code that defines the data and behavior for something, not unlike the code for a Part
or TextLabel
. That is what we'll be making.
"But you can’t make classes in Lua, silly…"
Wrong! Sort of, anyway.
Assuming you’ve worked in a language other than Lua (one with classes, specifically), you are familiar with the idea of making classes that have all manner of properties, methods, even events. You know that using these classes, you can make data structures of things you want to represent. A car might have color and horsepower properties as well as start and accelerate methods. If it’s particularly classy (pun intended), it may even have events that fire on collisions, passenger changes, etc.
Now, as you know, plain old Lua doesn’t have classes; however, you can create class-ish structures simply by creating a table:
local Car = { IsOn = false; Color = "Bright red"; Horsepower = 9001; Model = [Car model]; DriverSeat = [Driver seat]; Horn = [Horn sound]; Honk = function() Horn:Play() end; Start = function(self) self.IsOn = true self.DriverSeat.MaxSpeed = self.Horsepower print("Vroom, vroom!") end; Stop = function(self) self.IsOn = false self.DriverSeat.MaxSpeed = 0 -- Fancy European cars make these sounds, right? print("Ding, dong") end; }
As you can see, this table structure allows you to store various details about a given vehicle. Assuming you made a GTA-esque game on ROBLOX, you could represent each car with the above table, and use the Model
property to point to the car it represents. This practice is the basis for the implementation of MVC I wish to share with you.
The Model component of MVC, as I have stated, is the code that defines the behavior for a specific thing. Take the above car class for an example: it’s not worried about anything but the behavior and properties thereof. It’s not worried about giving players achievements for clocking hours in the game or knowing where other cars are; it is laser-focused on solving problems related strictly to making a car work as it should. This principle may well be applied to all code you write.
Let’s give it a go.
To boost your adventures in MVC, I have prepared a template place with everything you need to start using MVC right away. In it, you will find a preset folder structure under ServerScriptService
that defines where all the modules you make should go. You will also find a script named ‘Main’ and a module named ‘README.’ Feel free to check out the readme if you like.
We will start by making a module that returns a car model (like the one we made earlier) by way of a New
function. Observe:
-- Module ‘CarModel’ in ServerScriptService > Model return { -- carModel is the respective car in the workspace that this represents. The last two parameters are self-explanatory. New = function(carModel, color, horsepower) -- Code to change carModel color to color, maybe? return { IsOn = false; Color = "Bright red"; Horsepower = 9001; Model = [Car model]; DriverSeat = [Driver seat]; Horn = [Horn sound]; Honk = function() Horn:Play() end; Start = function(self) self.IsOn = true self.DriverSeat.MaxSpeed = self.Horsepower print("Vroom, vroom!") end; Stop = function(self) self.IsOn = false self.DriverSeat.MaxSpeed = 0 -- Fancy European cars make these sounds, right? print("Ding, dong") end; ToString = function(self) return string.format("%s car with %c horsepower", self.Color, self.Horsepower) end; } end }
Sweet! Now let's make a very simple spawn system whereby a car is spawned at a random location every ten seconds.
-- Module ‘CarSpawnModel’ in ServerScriptService > Model local CarFolder = Instance.new("Folder", game.Workspace) local SpawnLocations = game.Workspace:WaitForChild("CarSpawns"):GetChildren() local CarPrefab = game.ServerStorage.Path.To.Car local CarModel = require(game.ServerScriptService:WaitForChild("Model").CarModel) local function SpawnCar(spawnLocation) -- Make car and model to control it local prefab = CarPrefab:Clone() local carModel = CarModel.New(prefab, "Bright red", 9001) table.insert(Cars, carModel) -- Put the car at the specified location and into a car folder in Workspace prefab:SetPrimaryPartCFrame(spawnLocation.CFrame) prefab.Parent = CarFolder -- May its presence be known! carModel:Honk() end return { New = function() return { LastSpawn = 0; Cars = {}; InvokeSpawn = function(self) -- Only allow a car to spawn every 10 seconds if ((tick() - self.LastSpawn) > 10) then self.LastSpawn = tick() local spawnLocation = SpawnLocations(math.random(#SpawnLocations)) SpawnCar(spawnLocation) end end; } end }
Needs tweaking, sure, but it would be a nifty way to manage cars in that GTA-esque game I mentioned earlier; additionally, it serves as an interesting look into how one might apply MVC. May seem a bit excessive, but bear with me for now: onto the Controllers (nothing ado with mind-controlling slugs, I promise.)
Invasion of the Controllers (DUN, DUN, DUUNNN!)
The controllers, as I stated before, exist to create and control Models, especially in response to things that happen in the View and in other models. Using the car model we created, we may create something like the following Controller module:
-- Controller module 'CarSpawnController' in ServerScriptService > Controllers local CarSpawnModel = require(game.ServerScriptService:WaitForChild("Model").CarSpawnModel).New() return { Update = function() CarSpawnModel.InvokeSpawn() end; }
Pretty straightforward. All we're doing is making a new CarSpawnModel and calling its InvokeSpawn method repeatedly. That's all this controller needs to do.
Of course, you’d have to add the car assets, make the spawns, and tweak the code a bit to fit your needs, but this is sufficient to introduce you to the possibilities MVC offers. If you wanted to share code between scripts (for example, code that verifies a person’s clearance), you could make a model to do that and require it wherever needed. You could even have Controllers talk to each other by way of Bindings and Remotes. Perhaps coolest of all, you also just made your own car class that you can ‘instantiate’ with CarModel.New()
. Sweet!
Take note of the Update method, because that is one of several ‘special’ methods I have defined for this implementation of MVC.
The Big Kahuna
For most models, there’s a controller. The controllers, though, don’t do much at the moment. Obviously, something needs to require our Controller and run its Update method in order for it to do its job. That thing is a single script I affectionately call, ‘The Big Kahuna.’ Remember that ‘Main’ script directly under ServerScriptService? Check it out!
Assuming you’re fairly knowledgeable, you’ll notice straightaway that it basically just requires all the modules in Controllers and runs certain methods in each when something happens. At the start of the game, each Controller’s Start
method is called; when a player joins, PlayerAdded
is called; thirty times a second, the Update
method is called also, and so on.
Here’s the cherry on top: you can add your own methods too! (Radical, dude!)
More on that in the README under ServerScriptService.
Alright, pretty cool! Good on paper, but how does it stack up?
MVC is great because it breaks things into pieces, but it also helps you think big-picture. You don’t have to waste time and mental resources ramshackling hundreds of scripts together, because you’re spending that time instead defining logic in structures that contain all the information they need. Further, they can be easily tweaked to share that information with other models as needed.
It’s a somewhat abstract and unusual concept for many, and you really have to try it yourself to truly appreciate it. Having tried it myself, I can testify to its usefulness.
I’ve used a workflow similar to MVC in two projects of mine so far over the course of about five or six months, and I’ve found that not only have I been able to quickly add new features, but I’ve been able to tweak and maintain existing features incredibly easily. Most bugs I’ve come across usually took less than five minutes to identify and develop a solution for. What’s more, I’ve also noted significant reduction in resource consumption compared to previous, comparable projects.
Don't just take my word for it, though. Try it for yourself!
I truly hope that you will benefit greatly from using MVC. If you have any questions, comments, or ideas, feel free to shoot me a PM. Better yet, check out the wikipedia article on MVC.
Have a very nice day!
Warm regards, tkcmdr
(Kudos to my pals OldPalHappy, 1waffle1, BlueTaslem, and Link149 for proofreading! They’re my heroes ! :3 ).
WAIT! There's more...
Before you place MVC on a pedestal and use it religiously, I'd like to make a clarification I failed to make in previous versions of this blog post: the goal of this article is not to promote MVC alone, but to promote the wider concept of program architectures.
Again, MVC is useful because it forces you to break things up in a manner that is intuitive and easy to maintain, but there are many other program architectures out there that apply the same principles. In truth, MVC is actually pretty unwieldy on ROBLOX when compared to other architectures, MVA (Model-View-Adapter), for example.
Why then have I chosen MVC? Simple. It's the basis for most other program architectures out there. Once you're familiar with MVC and its principles, you're already adapted to a more architectural mindset -- you understand the reasons why people take the extra time to structure things well. That is what matters, and it's why I wrote this article.
So, go on out there and experiment! Use MVC, find its pros and cons, and look for other architectures that fit your needs better. Better yet, make your own!
Now you're getting it. c;
Commentary
Leave a Comment