Thursday, 24 April 2014

Corona and the Model-View-Presenter pattern (Part 1)

This is the first part in a short series on using a Model-View-Presenter (for the rest of the time,MVP) pattern in Corona. I will implement a simple Pong game using "MVP".

What is MVP ?

MVP is a design pattern. It's a tried and tested way of arranging your programs so that they are clear and maintainable.

The basic idea is simple, but counterintuitive for a Corona developer. There are three 'bits', shown in the diagram.

1) The Model - this is the data that represents your game. So in "Pacman" this would be the ghost and pacman locations, the maze layout and so on, and the code that manipulates it and accesses it.

2) The View - this is the display representation, what you actually see on the screen. That's all it does - there is no code for moving things about, no game code there. All it does is represent the model visually.

3) The Presenter - the middle man that does all the work.  Decides when the characters move, where they go, what happens when they collide etc. and updates the model accordingly.

As you can see from the diagram, the model (the data) and the view (the screen) don't actually know about each other. This is good programming practice, but somewhat different to how Corona operates ; Corona mixes the two up, especially when you add the Physics engine in.

You aren't limited to single things. So a game which has (say) a main screen, a HUD showing status information and a Radar could have three different views, one for each bit.

Why bother ?

Many Corona developers will look at this and say, well, what's the point ? Corona, partly because of the way it is designed, encourages big lumps of spaghetti code that does everything. When you write larger programs, or want to maintain a program, it's much easier if you can separate things into workable bits that you can test independently.

Design Patterns usually use Classes and Objects, though in this 'Pong' game I am going to program it without them, because it introduces an extra layer of confusion.

These are just ideas. Read it, try it if you like it. Try variations. There is no 'right' way of doing things.

Try it out

I'm going to create a very simple 'Pong' one player game. It is very basic, and as such looks ridiculously over-engineered :) You could say, well, with the Physics Engine you could write it in half a dozen lines of lua, and you probably could.

So to start with, create a Corona project, with a main.lua file. Then create four separate files - model.lua, view.lua and presenter.lua. These are going to contain the three parts of the program.

main.lua contains this:

local model = require("model") -- pull in the model and the view.
local view = require("view")

model.initialise() -- initialise the model and the view

view.update(model) -- update the view from the model.

This is only a first step - for this first part we have no presenter, only a model and a view.  So it will start and display, but it won't actually do anything.  All this code does is load the model and view modules, initialise them, and update the view.

The only interesting line is the last one, which updates the view using the data in the model. I do this by simply passing the model over so the view can access it. It is considered good programming practice here to use an interface for this - so the view only gets the bits of the model it needs, but lua doesn't really support interfaces.

(This is an example of pragmatic coding. I rely here on the coder (me) not to abuse the fact that the view has the whole model)

model.lua contains this:

local model = { }

function model.initialise()
model.bat = { x = 160, y = 440, width = 60 } -- initialise the bat

return model

The model only has a representation of the bat in it - where it is and how wide it is .We don't care how 'tall' it is, whether it is represented by a rectangle or a sprite, or anything else. The View does that bit. This has the handy advantage that if we choose to change the visual representation of the bat in some way, we only have to change the view. (Later on, I will show this in action, changing the control method without actually changing very much of the code)

view.lua contains this:

local view = { }

function view.initialise()
view.batObject = display.newRect(0,0,10,10) -- create the bat object. 
view.batObject:setFillColor( 1,1,0 )

function view.update(model)
view.batObject.x = model.bat.x -- update the view from the model.
view.batObject.y = model.bat.y
view.batObject.width = model.bat.width

return view

A view is created at the start - it's a yellow rectangle which we just plonk anywhere, we don't care how wide it is. We do care how high it is, mind, so the fourth parameter does matter.

The update function takes the model information above, and transfers it into the various bits of the view.

Run it and you get a yellow bat at the bottom of the screen (it's a rotated Pong).

You can get it from GitHub

Note: this uses the new Graphics system. I'm not sure if everyone has it yet ! If not, then you will need to set your fill colours to 255,255,0 and the batObject will have to have its reference point set to the middle. I think (!)

But .. but .. but .. but ..

Now, at this point, I can guess pretty much what many people are thinking. There's a lot of duplication here, there's actually two sets of everything - x,y, and width in both the model and the bat object, and you are copying lots of stuff about for no real reason. (There are ways of reducing this so it doesn't look quite so bad).

There are advantages though. The two coordinate systems do not have to be the same, for example, though they are here.

Why is this useful ? Well, suppose you release this to the app stores (let's face it, it's better than Flappy Bird !) and you want to put an advert on the top of the screen, but you forgot and there's no space. You can 'fix' it by scaling your vertical coordinates without changing the model or the game code (which is in presenter.lua, currently there's nothing there at all).

So if you replace the view.update() line with

view.batObject.y = model.bat.y / 2

you instantly free up half the screen,but the game works exactly as it did before. The game bit (the presenter and model) still operate in the full screen, but the actual drawing bit only occupies half of it.

Other useful consequences ; well again, think of Flappy Bird (ideally not just after eating). Flappy Bird has a physical world - the screen layout - and a logical one - an infinite line of pipes and a bird. The physical one is a representation of the logical one. If you do it this way, then your model only has to know about the bird and pipes - the scrolling bit is done by the view.

You could even convert it to 3D, and the game and model part would still be the same, just the view would have changed.

Anyhow, that's it for this time. Next time, we'll actually get something moving, and create some presenter code.

(According to my outline notes, there will be seven parts to this)

No comments:

Post a Comment