Saturday 26 April 2014

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

In this episode (?) we are going to get the bat moving at the bottom of the screen, and introduce the "presenter". The code in part 1 only had a view - the display bits and a model - the data bits, but no code to actually do anything.

This code will live in the presenter.

Please be aware this code is quite deliberately over engineered. In practice you wouldn't break it up quite as much as this - I'm trying to make it clear what is going on. Hence it does look a bit over the top at times, I'm writing far more code than I would normally.

What we want to do is to get the bat moving backwards and forwards on its own at the bottom of the screen. (in the game, we will touch the bat to make it change direction - okay, so Mass Effect III isn't under threat from this game ....)

So we need more things in the model. As well as its current 'data', the bat has a direction and a speed. So open model.lua and add the following code to model.initialise() (I will try to put 'old code' in italics throughout)

function model.initialise()
model.bat = { x = 160, y = 440, width = 60 } -- initialise the bat
model.bat.dx = 0 -- create model.bat.dx, model.bat.speed
model.bat.speed = 5  
model.setBatDirection(1)
end

We could, of course, just add dx = 0, speed = 5 to the model.bat "initialiser". We have a couple of 'helper functions' as well.

function model.setBatDirection(newdx)
model.bat.dx = newdx
end

function model.moveBat()
model.bat.x = model.bat.x + model.bat.dx * model.bat.speed -- move the bat.
if model.bat.x > display.contentWidth - model.bat.width/2 then -- check off the right
model.setBatDirection(-1)
end
if model.bat.x < model.bat.width/2 then -- check off the left.
model.setBatDirection(1)
end
end

You could put these in the presenter if you wanted - you could really lose 'setBatDirection' altogether and access the model directly. (This will really upset some OO fanatics ....)

There aren't really any fixed rules or anything about this - it depends how comfortable you are with whats going on, and how much you are likely to change the code in the future and where you think code belongs. These two functions are more about updating the model rather than game handling code itself, so they are better off in the model.

We don't change our view at all - it still looks the same. We do change the main program very slightly.

local model = require("model")
local view = require("view")
local presenter = require("presenter")

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

Runtime:addEventListener( "enterFrame", -- added code to update the presenter.
function(e) presenter.update(model,view) end)

Firstly we are now getting the presenter module - we didn't have it before, and it is being initialised. I have also removed the line that updates the view, it is the responsibility of the presenter to do this.

The last line causes the the presenter.update(model,view) line to be called on every frame (currently 30 times a second), so the presenter can keep updating the model, handling actions, updating the view etc etc.

Finally, and most importantly, we have the presenter. This is the code that actually "does stuff" and is all-new.

local presenter = { }

function presenter.initialise() -- our initialisation function does nothing.
end

function presenter.update(model, view)
model.moveBat()          -- move the bat.
view.update(model) -- update the view from the model
end

return presenter

Again, there is very little code here. We create a 'presenter' module, there is an initialise() function which does absolutely nothing at the moment, and our update routine, which is called at 30fps.

This does two things - it updates the model - moving the bat - and it then updates the view.

This is the big difference from Corona SDK in a nutshell. In Corona you'd just update the x position of the display object and it would move. The moveBat() call would do absolutely nothing to the visual part of the display - it just updates the data representing the display. The view won't update until you tell it to.

Now, you may well ask, what is the point of this ? What do you get by splitting it up ?

It's a bit of a balance - separating it allows you to easily change things without it having lots of knock on effects, and keep track of things more easily.

Against that, it takes more code, and more processor time to do it, and there is some duplication - x,y and width are kept in two separate places, both in the data model and the objects that make up the view.

Often the best approach can be a bit of a hybrid - so if your game as little bits of extras that aren't really part of the game - for example an explosion, then the view could handle this on its own - it's a graphic effect that doesn't effect the game core at all (in most games anyway !) - this sort of thing you learn by trial , error and experience. It makes changing things and fixing bugs easier - we will show this later on when we change a couple of things in the finished game.

Code for this part is at https://github.com/autismuk/MVP-Intro/tree/master/Part2

Note: someone asked me about Composer/StoryBoard (and Director, I guess ?), how does that fit in to this. Well, they are really part presenter and part model, but mostly view. Composer and the others handle different display sets at different times and transition between them.

However, they do have some concept of 'state' - where we are in the game process, and as ever with Corona SDK, the display objects have their own data as well. In a classical pattern, the data (x,y,rotate,xScale and so on) would belong in the model, the state (what current scene) in the presenter, and all the creating / eventing stuff in the view.

In the next episode, we will control the bat.

No comments:

Post a Comment