| |||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||
Print This Article REALbasic University: Column 039
RBU Pyramid XIIThis week we're going to do something that sounds simple, but isn't: add a high score list to our game. This isn't brain surgery, but as you will see, it effects many areas of our game, making the implementation complicated. In fact, we won't even finish this today -- this will be a two-part lesson. Be prepared as we jump around the code a lot! The techniques used for adding this high score list are simple and should be reusable in other games.
Adding a High Scores WindowThe first thing we'll need is a special "high scores" window where we'll display the list of top scores. This could be a simple dialog box, but that's not as convenient for the player since the scores can only be seen when the game's over. Instead we'll go the extra mile and make our window a floating window -- that's a special kind of window that floats above document windows and is typically used for tool palettes. That way the scores can always be visible (or not, as the player prefers). With your RBU Pyramid project, add a new window (File menu, "New Window"). Give it the following settings: ![]() (Note that the full title property reads, "RBU Pyramid All-Time High Scores".) Now open HighScoreDialog (double-click on it) and drag on a listBox control. Give it these settings: ![]() The contents of the initialValue field aren't displayed, so here's what you put there: "Rank Name Score Date". Please note that those aren't spaces separating each item -- those are tab characters.
Now that you've done that, let's initialize our listBox. Open scoreList in the Code Editor (double-click on it). Go to the Open event and put this:
All this does is set the appropriate columns to center alignment. We don't want column 1 to be centered, since that's the player's name and it looks best flushed left (the default). Next, go to sortColumn of scoreList and put in these two lines:
That just prevents the user from sorting the listBox. By default, any multi-column listBox in REALbasic will automatically sort itself when the user clicks on the various headings. In our case, we don't want that -- high scores should always be displayed best-to-worst. So we intercept the sort event and tell RB that the headingIndex is really -1, meaning that the user hasn't clicked on a specific column (and therefore there's no need to sort anything).
Remembering a Window's LocationAs an extremely particular and organized Mac user, one of my pet peeves is programs that don't remember where I put a window. If I resize a window and put it someplace, it should be in that same place the next time I run that program. This applies especially to floating palettes such as the high scores list we're working on. There are countless ways you could use to save a window's location info, but we'll take a simple direct route. It's not necessarily any better or worse than any other, though it does integrate well with my preference system. What we'll do is save our window positioning info in a global property called gScorePos. It's of string type, so open globalsModule and add a new property (Edit menu, "New Property"). Type in this gScorePos as string in the dialog that pops up. Let's also add another global property: gScoresShowing as boolean. Good. Now go back to HighScoreDialog and let's add a new method (Edit menu, "New Method"). Name it saveWindowInfo and put this code into it:
All this does is save the window size and location, separated by commas. Simple! (Remember, the special self function refers to the window owning the routine. In this case, that's HighScoreDialog [saveWindowInfo is inside it], so self.left is exactly the same as HighScoreDialog.left.) But wait a second -- what's a comma? Does REALbasic know what that is? No, it does not: go back to globalsModule and let's add a constant (Edit menu, "New Constant"). Name it comma and give it a value of "," (no quotes). I warned you we'd be jumping around today! (And we've just gotten started.) Now we run into a tricky little situation. Basically, what we want to have happen is that when the user resizes or moves the HighScoreDialog, we save that info. So it seems like all we need to do is put in a call to saveWindowInfo within each of the Resized and Moved events, right? Not so fast. What happens is that when the window is initially opened (opened for the first time), it is resized and moved to the correct position -- and doing that causes REALbasic to fire the Resized and Moved events! If those events save the window position, they overwrite (erase) the original info that we haven't used yet. (If that doesn't make your brain spin, I don't know what will!) This bug took me a while to find when I wrote the first draft of RBU Pyramid: everything looked fine, but the program wasn't saving the window position info. I finally had to step through the program step-by-step to figure out what was going wrong. The solution, however, is simple: we just need a flag to tell us when the window is initially opening. So let's add a property to HighScoreDialog (Edit menu, "New Property"): opening as boolean. Now go to Resized and Moved events of HighScoreDialog. Put in this same code for both:
See how that works? If the window's initially opening, it won't call saveWindowInfo. In other words, it will only save the new window details if the user's changed the window's position or size. But for this to work, we must do some initialization stuff when the window is opened. So go to the window's Open event and put in all this stuff:
See how we've framed the window resizing stuff with opening = true and opening = false statements? That way when we're programmatically adjusting the window it won't save that location info. As you can see, it's easy to extract the window info from gScorePos. Since each field of info is separated by a comma, we use that as our divider and tell nthField() to grab each field one by one. Since nthField() returns a string and we need a number, we use val() to convert the string type to a number type, and then we assign that number to the appropriate window property. About the only way to screw this up is to do things in a different order: make sure that if your window data is saved in left, top, width, height order that you retrieve it in the same order! When HighScoreDialog is closed, we'll need to save the current window position and set the gScoresShowing property. This goes in the Close event:
Guess what? We're done with HighScoreDialog. You can run the program and it will work, but of course we haven't done anything with HighScoreDialog yet, so it won't seem any different.
Adding a MenuThe player can't see the high scores window because we haven't displayed it, or given the player any way to display it! So let's add a menu command to display the window. Within your RBU Pyramid project, double-click on the Menu object. You should see a menubar within a little window. To the right of the Edit menu within this window, there's a gray rectangle. Click on it to highlight it. Like this: ![]() Now type in "Options" (no quotes) and press return. That should have added an Options menu to your program. On the blank line underneath that, select the blank area and type in "Show High Scores..." (no quotes). ![]() You can give the menu item a Command-key shortcut by typing "H" (no quotes) on the CommandKey property field. Now all we've done is tell REALbasic we need this menu item -- we haven't told it do anything with it yet. Since the OptionsShowHighScores option is a command that is application-wide (not restricted to gameWindow), we want to add a handler to this command within our app class. So open App class and add a Menu Handler (Edit menu, "New Menu Handler") -- OptionsShowHighScores should be available on the popup list. Select it and click OK. REALbasic should have added the menu handler underneath the existing AppleAbout handler. If you click on it, we should be able to add code to it on the right side:
All this does is if the window is showing, it closes it, or if it's closed, it displays it. Simple! But if you run the program now, you'll find that our new menu command is grayed out! What's up with that? By default, REALbasic keeps menu items inactive. You must specifically tell RB that the command is enabled for it to be available for the user. Go to the EnableMenuItems event of app. There should already be a line of code there. Underneath that, add this (separated by a blank line or two, if you want):
See how clever we're getting? We not only enable the menu item, but we change the text of it so when the window is visible it says, "Hide" and when it's hidden it says "Show." Very slick! But when you try running the program now, it gives you an error, doesn't it? It says "unknown identifier: gameWindow.showScores". That's because we never created a showScores method within gameWindow! Open gameWindow and add a new method (Edit menu, "New Method"). Name it showScores. For now, put it highScoreDialog.show for the code -- next week we'll fill in the rest. That should give you a program that's runable. No actual high scores are displayed (yet), but you can move and resize the window and show/hide it at will. (Of course, the details of the resize are not saved in the preference file yet -- we'll add that capability next week.) If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.
Next WeekWe add high score loading and saving routines.
LettersThis week we hear from Paul Thompson, who writes with a common REALbasic situation:
You're so close, Paul. The answer's simple... an editField is a class, a class of "editField". (If you select an editField and look at the "super" property on the Property list it will say "editField".) So the solution is simple. "EditField" is name of the class! This works:
I do this all the time -- as you've figured out, it's a valuable technique! A variation of this is that you can store an editField's reference inside a variable. For example, you could create a property "cf as editField" and then within an editField's GotFocus handle put in "cf = me". What good is that? Well, let's say you have a window with multiple editFields. Let's say you have a "Select All" command (like you do above). You want the command to work in all fields, but to do that, it must know which is the current field. By storing the current field in the cf variable, your Select All routine can just be:
You'd probably also want to put a "cf = nil" in each editField's LostFocus event. That way when the user moves out of an editField, cf is set to nil, and when they move into a new editField, cf is set to that editField. And if you set up your editFields as a control array, you only have to put in this code in once no matter how many editFields you have! Hope this helps. Next, we hear from David J. Gauthier, who has a question about the REALbasic documentation:
That's an interesting question, Dave. Unfortunately I don't know the answer as I never bought it. I think it might be the same as the downloadable PDFs, but simply printed, but I'm not 100% sure on that one. Either way, I suspect the docs are decent, but not outstanding -- you'd be better off with one of the REAlbasic books out there (either "RB for Dummies" or Matt Neuburg's more advanced book -- see links at the top of this page). You might want to join one of the REALbasic mailing lists and post this one to the crowd there -- I'm sure a few have bought the printed docs and could help you. If any RBU readers have a comment on this, feel free to let me know your thoughts on the subject. Our final letter this week, comes from Michael Krzyzek, who has a real doozy of a question:
Excellent question, Michael! Actually, the corners of my cards in RBU Pyramid are active: it's just the corner area is so small it's rare you're able to click within the few pixels between the rectangular canvas edge and the curved corner. First, REALbasic does not support a region-type graphic. Yes, that's a bummer, since the Mac OS has support for regions and cool routines like "is this x,y point within region y?" making what you want to do easy. It's harder in REALbasic. Now you could figure out the mathematics and do all the calculations for this kind of thing manually within REALbasic, but it would complicated and (probably) slow. A simpler method, which is what I would use, has some disadvantages, but should work in most cases. You mention you have a mask that's the shape of a particular continent. A mask (in REALbasic) has white areas transparent and black areas solid. Since the mask is just another picture, you can examine the clicked point inside the mask and see if the point is black or not. If it's black, we're inside the mask area! I've written a tiny program demonstrating this: click on North America and it beeps: ![]() But if you click within the clear area of Gulf of Mexico or the Great Lakes, it doesn't beep. Hopefully this will get you started. For anyone who'd like this code, here's a project file to download. About the Column REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers. Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.) About the Author See the REALbasic University Archives
REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.
| |||||||||||||||||||||||||||||