Runtime Revolution
 
Articles Other News

Cannonballs! Part 4.1


by Robert Cailliau


The previous pages were a little heavy, let's do something light now:

Filling the barrel with gunpowder

We want a scrollbar control to set the amount of gunpowder and a button to fire the cannon. Create the scrollbar for the gunpowder similar to what you did for the elevation slider:

Select the scale tool, drag a scale-type scrollbar, call it Powder. Set the endvalue of the Powder scrollbar to 100.

Set the script of the Powder scrollbar to:

on ScrollbarDrag fValue

put fValue into field "Powder"

end ScrollbarDrag

 

Make a label field, call it hPowder, set its contents to Powder. Using the inspector, select Text Formatting and set the text alignment to right align:

Create a normal field, call it Powder, make it transparent, lock its text.

Create a button, set its name to Fire, set its label to Fire! (with the exclamation mark). This is new: the button will display its label on the screen, but it will be called Fire (its name) when we use it in handlers.

Make the cannonball

Create a small circle graphic, call it Ball, set its fill colour to black and its border colour to red. I made mine 5 pixels by 5 pixels.

Arrange the objects

Place all this stuff so it looks like this:

The ball is very small. If you have problems dragging the ball around, then maybe just use the inspector to set its location. If you have problems selecting it, use the Application Browser.

Save your work. It may be a good idea to quit Revolution and to make a copy of Cannon.rev into the archive folder. Then double-click Cannon.rev again to continue.

Making it work

The only scripts we have so far are those that let us decide the elevation and the amount of gunpowder. There are only three lines of code so far!

When we press the Fire button we want to let the ball start from the nozzle. It has to fly off in the direction the barrel points to and with a speed that depends on the amount of powder.

In object-oriented programming, it is the ball that knows how to move. The Fire button only tells it to do so. Each object should do only things it knows about.

The ball knows its location and speed but it should not know about the cannon. The script of the ball should have a handler called Fly. To that handler we must give the position and the speed at which the ball starts, but nothing else.

The button Fire should look at the barrel. It should find the elevation and the amount of powder, calculate the location of the nozzle and the ball speed, and then it should tell the ball to fly.

The barrel does not know anything yet. We will have to invent two new properties that the barrel image does not have: elevation and powder. Change the handler of the Elevation scrollbar to this:

on ScrollbarDrag fValue

set the angle of image "CannonBarrel.png" to fValue

put fValue into field "Elevation"

set the pElevation of image "CannonBarrel.png" to fValue

end ScrollbarDrag

(I use the prefix letter p for properties).

Change the handler of the Powder scrollbar to this:

on ScrollbarDrag fValue

put fValue into field "Powder"

set the pPowder of image "CannonBarrel.png" to fValue

end ScrollbarDrag

Run the program (). Drag the elevation to 30 degrees and set the powder to around 60. Select the selection tool (, this also stops the program) and look at the inspector for the barrel image. Select Custom Properties:

The barrel has two new properties!

The Fire button needs to give four values to the Fly handler: the x and y position of the nozzle and the x and y components of the speed. Right-click on the Fire button and select Edit Script. Type this handler:

on MouseUp

-- get info from the barrel:

put the pElevation of image "CannonBarrel.png" into lElevation

put item 1 of the loc of image "CannonBarrel.png" into lCentreX

put item 2 of the loc of image "CannonBarrel.png" into lCentreY

put (the formattedwidth of image "CannonBarrel.png")/2 into lLength

-- compute the location of the nozzle:

put lCentreX + lLength*cos(lElevation*pi/180) into lNozzleX

put lCentreY - lLength*sin(lElevation*pi/180) into lNozzleY

set the loc of graphic "Ball" to round(lNozzleX),round(lNozzleY)

end MouseUp

Press the Apply button on the script window, save your work and select the browse tool to run the program. Set the elevation to something, then press the Fire button. The ball should move to the nozzle. Select other elevations and press Fire. The ball should always position itself at the barrel nozzle.

Note: this is of course a useless script for the Fire button, but it shows us that our computations for the nozzle are correct. The line  set the loc of graphic "Ball" to lNozzleX,lNozzleY has no use later on. It is a temporary line that lets us make progress and check that we are still doing well.

Let's look at this handler in detail:

The first line is a comment. Comments start with -- and run until the end of the line.
The next line just takes the barrel's new property to store it into a local variable for later use. I used spaces to line up the code so it is easier to see that four statements deal with the barrel.

The next two lines take the components of the location of the barrel's centre and put those into local variables.

The fifth line uses the property formattedwidth. Why could we not just use width ? The barrel is originally 85 pixels wide by 33 pixels high when it points horizontally. But when it points upwards, it will be 33 pixels wide and 85 high! At angles in between it will have strange widths and heights. The formattedwidth always gives the original width of the unrotated image, and that's what we need.

Then there is another comment and two lines where we use all the info to compute the location of the nozzle. I repeat here the diagram of how we thought this up:

Note: the second line for computing the location of the nozzle uses a minus-sign, because the y-coordinate is measured from the top. When the barrel swings up, the y of the nozzle diminishes! (I told you that we would be irritated by this stupid screen convention).

Now, why did we not get the elevation and the powder directly from the fields Elevation and Powder?
We could indeed have done that, but I'm teaching you object-oriented programming, not shortcuts.

Ugly

Ah, but if objects ought to know things about themselves, then should the barrel not know where its nozzle is? Yes, it should! The handler we just wrote is quite ugly, most of its statements should be in the script of the barrel. We put them there in a computed property.

Computed Properties

We have given two new properties to the barrel: its elevation and its powder content. You can see their values in the inspector. We can't however make the location of the nozzle a simple property: we must compute it. Revolution allows us to do that: we will put the statements to compute the nozzle location inside the barrel script, as a special getProp handler.

Select the barrel image and edit its script:

getprop pNozzleLoc

put the pElevation of me into lElevation

-- compute the location of my nozzle:

put item 1 of the loc of me into lCentreX

put item 2 of the loc of me into lCentreY

put ( the formattedwidth of me )/2 into lLength

put lCentreX + lLength*cos(lElevation*pi/180) into lNozzleX

put lCentreY - lLength*sin(lElevation*pi/180) into lNozzleY

return round(lNozzleX),round(lNozzleY)

end pNozzleLoc

The getProp is a handler for making a computed property. Revolution has three handler types:

  1. procedures or routines, we have already met them, they start with the word on.
  2. functions, they start with the word function and we will meet them in another tutorial.
  3. computed property definitions, they start with the word getProp.

Note how the word me is used in the handler: me means the object where the handler sits.

Note how we use return to hand back the coordinate pair as the result of our calculations.

Note how the systematic use of prefix letters helps distinguish the different Elevations.

 

Now the handler in the Fire button can be elegantly simple:

on MouseUp

set the loc of graphic "Ball" to the pNozzleLoc of image "cannonBarrel.png"

end MouseUp

This statement looks just like any other statement that uses a property of an object. I used the prefix letter p so that we can see from the name that it's not a usual property.

Object-Oriented programming note

We have written very few lines of code. All code is nicely tucked away in small handlers. The objects behave in the way we want and we know exactly where to look in case of problems.

 
©2005 Runtime Revolution Ltd, 15-19 York Place, Edinburgh, Scotland, UK, EH1 3EB.
Questions? Email info@runrev.com for answers.