Saturday 26 April 2014

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

In part 3, we are going to add the ability to control the paddle - it is a very simple control, it is going to reverse direction whenever you 'tap' on the bat. It's not the most exotic control system ever developed, but it shows the point.

In MVP the communication, whatever it is, between the model or view and the controller goes both ways.  So we need the 'view' to be able to send messages back to the 'presenter' - a "the bat's been tapped" message.

To do this, the view needs to know about the presenter, so it knows where to send the message. To do this, we have to make a little change to main.lua to allow the view to have a reference to the presenter.

model.initialise()  
view.initialise(presenter)
presenter.initialise()

at the same time, you can add the bit I forgot at the top to hide the status bar :)

display.setStatusBar( display.HiddenStatusBar )

We have added the presenter as a parameter to the initialisation code for the view, so the view will be changed (slightly) to support this. We also need to add an event listener for the bat object, so it picks up taps (note, the parameter list has changed as well as adding the two new lines)

function view.initialise(presenter)
    view.batObject = display.newRect(0,0,10,10)
    view.batObject:setFillColor( 1,1,0 )
    view.presenter = presenter
    view.batObject:addEventListener( "tap",view ) 
end

This stores the presenter in a variable called 'presenter', and creates a reference to a table listener event, that will call a function view.tap when it is tapped.

This function "sends a message" back to the presenter

function view:tap(e)
self.presenter.tapMessage()
end

It actually does this by calling a method 'tapMessage' rather than using listeners (aka the Observer pattern) - it calls a function in the presenter "tapMessage".

So those are all the changes to the view. It knows about the presenter, and it calls 'tapMessage' when it receives a tap.

Now, we need to make some changes to the presenter.

Another modification is for the update routine in the presenter to store a reference to the model, so it knows where the data is (otherwise the tapMessage function will not know which model to work on).

function presenter.update(model, view)
presenter.model = model
model.moveBat()
view.update(model)
end 

Last, we need to had a 'handler' for that 'message' (in practice, a function) which 'receives' the message and reverses the bat direction.

function presenter.tapMessage()
presenter.model.setBatDirection(-presenter.model.bat.dx)
end

When the tapMessage is received - in reality, the function is called, it sets the bat direction to the opposite bat direction.

I have mixed up access here, which is perhaps not ideal - in that the bat direction is read directly from the model but it is updated using a function. In good programming practice, you should really use accessors - setters and getters - for all the data, but this does create a lot of functions and can slow things down. It is a personal decision whether to access the model directly or via functions or not.

In the many years I have been programming - I started with one of these - I've seen so much code which was very well engineered but wouldn't actually work effectively on the hardware we had. Saying 'well, we can wait for faster computers' (yes, Gates, this means you) is not a good option.

You could also make a good case that there should be a function model.reverseDirection() which is called to do this. Again, there are no fixed rules - it is just one way of approaching it. This isn't a 'this is how you do it' - it is more 'this is a way of thinking about doing it'.

As long as you are consistent it doesn't matter. Generally, if it is likely to vary significantly - encapsulate it. In this particular case, writing presenter.model.bat.dx = -presenter.model.bat.dx would be okay.

So, it should now, when running, move the bat backwards and forwards, and when you touch it, or click it on the simulator, it should reverse direction.

It should look like this (Corona Simulator) ->

Exercise 1

... except, there'll be no bricks. It'll just be a black background.

I thought I should let you do some code of your own, rather than just following me, so the exercise for this 'lesson' is to put the background in.

Q: Where does it go ? Is it part of the view, model, or data ? Any or all of them ? Does it have associated 'game data' or 'game logic' ?

There is a working version in the repository at https://github.com/autismuk/MVP-Intro/tree/master/Part3 (which also has a brick graphic). Tiling an object in Corona isn't quite as easy as you might think :)

In part 4, I'll add a new view and model item - the ball.

Exercise 2 (Easy)

When running this I actually found it quite hard to click on the bat. How do you make the bat deeper (e.g. fatter vertically !) so it is easier to click ? Should this affect the view, or the model ? or the presenter ?

No comments:

Post a Comment