Games * Design * Art * Culture


Tuesday, July 27, 2004
Restaurant Empire
Sim/Tycoon games don't get a lot of respect, at least from the hardcore. They're viewed as not sufficiently cool--slower paced, not dependent on twitch mastery, and mainly for the kids. This has always struck me as odd, at least the "for the kids" part; after all, in terms of interface, algorithmic intensity, and strategic planning, these games are far more demanding to play than many other game styles. Except, of course, that they don't require you to live or die on the basis of your ability to manipulate the controls quickly.

The ur-game of this style was Meier's old Railroad Tycoon, now in its third version, and shorn of its connection to Sid. And the most successful game of its type was Chris Sawyer's Roller Coaster Tycoon, at one time the best-selling PC game, despite being coded in assembler (forsooth) and featuring retro sprite graphics that looked dowdy even in the late 90s. It had basically only one thing going for it: It was a hell of a lot of fun to play.

I still like games of this type, and Trever Chan's Enlight Software has been doing a number, most recently Restaurant Empire, which doesn't seem to have gotten a lot of attention. Being a foodie as well as a gamer, I decided to give it a try.

Here's what's good about it:

Restaurant Empire gives you a good sense of managing a restaurant. You hire and fire staff, construct a menu, place tables, decor, and kitchen equipment, and open the doors. Over the course of a day, people wander in and order. As is typical in games of this type, each customer's desires, wants, and reactions are tracked in detail, and you can click on any guest to see what he or she is thinking about (often, about the rudeness of staff or delays in their order). You can also see what peoples' main complaints are, general level of satisfaction, and so on. You try to increase the popularity of your restaurant by upgrading the menu and decor (as profits permit), adding new recipes and deleting less popular ones, learning about businesses that can provide premium ingredients, and so on.

The main game is a series of well-planned, linked 'levels,' each requring you to reach some benchmark within a period of time to 'win' (e.g., make $15,000 in profit in a single month). The level system provides a 'programmed learning' approach--that is, you're introduced to the details of game management over time--as well as a sort of backstory that provides a degree of motivation, and some characters (like your uncle, a retired restauranteur) who provide advice.

After a time, you crank up the time scale so that a day goes by more quickly (one of the game's conceits is that a 'day' is equivalent to a month--that is, you open the doors, serve lunch and dinner, and close, and the game counts up your receipts and expenses and multiplies by 30). Occasionally, you pause, or slow down the time scale, to monitor what's going on in more detail, and perhaps make a few changes to menu, staffing, seat layout, or restaurant equipment, to operate more effieciently. The actions of your staff and customers, the audio of a busy restaurant, are attractive, well done, and entertaining.

In other words, this is a very well conceived game; a priori, one might assume that a game about restaurant management would be dull, but this isn't. It helps if you've fantasized about running a restaurant, of course--a common fantasy of foodies, if not of Counter Strike players.

Here's what's iffy about the game:

Enlight, the game's developers, are located in Hong Kong. I have to assume that this is why they have chosen to translate all recipe names into English, even though you run a French restaurant at first, an Italian one later, and an American-style place last of all. I found myself puzzled by many recipes, and had to perform an awkward back-translation to figure out what they were talking about. White bean stew? Yuck... Oh! They mean cassoulet. Why didn't they say so?

Another awkwardness is the way in which you learn new recipes. Every so often, one of your customers will call you over and offer to sell you a recipe for some amount of money. If you agree, it is added to your repertoire, and you may, if you wish, add it to the restaurant's menu. Now, I don't know about you, but I suspect that David Bouley is not in the habit of buying recipes off his diners. The reality is that chefs experiment until they hit on something they find appealing. This system works, in a purely game sense, but for me it broke the fantasy a bit.

Another aspect I found less than optimal; as is typical in sim/tycoon games, you don't generally control your personnel directly. You hire them for particular jobs, and they perform them over the course of the game, but you do not direct them to perform specific tasks. I found this frustrating at times; on more than one occasion, I wanted to tell someone "Bus that table, goddamn it, dirty dishes have been sitting there forever," or for that matter, "Get downstairs to the kitchen, bitch, table six's food is getting cold." Which would indeed be within my purview as the owner/manager of a restaurant. I mean, I did understand that these actions didn't really fit into the game's overall metaphor, but it was still a bit frustrating.

Here's what's actually bad about the game:

At least on my machine, it crashes to the desktop pretty frequently. Not so frequently as to make the game unplayable, but, say, once an hour or so. I have played and enjoyed many PC games that do this--Europa Universalis being another--but it is annoying, an annoyance that is only tolerable in games that autosave frequently. Restaurant Tycoon, however, autosaves only at the end of a level, and if there's a away to set it to do so more frequently, I haven't found it. You can certainly save frequently yourself, but in the hurly burly of a busy lunch crowd, it's easy to forget.

This is, in fact, why I eventually stopped playing.

On the whole, however, I liked this game a great deal. It took some creativity to come up with the idea of a restaurant management game, and to figure out a way to make it work; the interface and basic gameplay is smooth and well implemented, the level structure works well, and I found it fun to play. It would definitely have been improved by retaining French and Italian names for French and Italian dishes, a more sensible approach to learning new dishes, and a little ability to control staff more directly (Roller Coaster Tycoon at least lets you pick up your cleaning staff and drop them directly on a pool of vomit or somesuch), but those are quibbles. The excessively frequent crashes are more of an issue; while its possible that my machine is just configured more oddly than most, I suspect that the developers must have been aware during the final stages that a number of crash bugs had not been smashed, and under the circumstances, it's somewhat mysterious why it didn't occur to them to at least allow players to autosave with greater frequency--a kluge, not a solution, but it would have made a substantial difference in terms of the ultimate player experience.


Comments []

Wednesday, July 21, 2004
N-Gage @ $99
So evidently the N-Gage QD will be $99, with a year's contract with T-Mobile. This is what it should have been all along, of course; the original launch price of $299 was absurd, the quickly-discounted price of $199 was still too high, and Nokia missed a bet (in the US) by not establishing a subsidy arrangment with one of the carriers at the inception.

The device itself addresses the main complaints about the original--you don't have to hold it on edge to talk, and the game cards are now hot-swappable. Cost of manufacture is reduced a little bit through the omission of FM radio reception and the ability to play MP3s. It also no longer comes with a stereo headset (and, I presume, no longer has the connector for one on the device itself). Presumably, market surveys showed these were not popular draws for consumers on what's mainly a game device anyway.

The Gamespot report linked above erroneously states that the QD has a camera; it doesn't.

My basic analysis: If you want a Series 60 phone, this is a great deal--The N-Gage QD is far and a way the cheapest one available at the moment, and it's got all the functionality of, say, the 3650 (less the camera). Meaning you can use it as your PDA as well as a phone and game device. Of course, if you want the $99 price, you're stuck with T-Mobile, which has spotty national coverage--personally I like AT&T better, and while you can buy an AT&T SIM card and use them instead, you'll wind up paying $199 without the T-Mobile subsidy.

The basic problem remains, though: no console system in the history of the game industry has ever recovered from a bad launch, and while Nokia seems determined to keep trying, well... It doesn't help that the Nintendo DS and Sony PSP are coming in the not too distant future, so people considering a new handheld may just decide to wait.

Another issue in North America, though not the rest of the world, is that N-Gage is GSM-only, meaning if you're currently on Sprint, Verizon, or Alltel, and want to get an N-Gage, you have to switch carriers. Ideally, you'd want everyone to be able to use it without having to jump through hoops--so in an ideal world, there'd be a CDMA version. Historically, Nokia has been weak in the CDMA world, but they're currently making a push to increase sales of CDMA handsets (and have announced and released quite a few over the last year). I think it would be smart of them to release a CDMA N-Gage, though I don't expect it to happen.


Comments []

Friday, July 16, 2004
Nokia is a river
...in central Finland.

Some fairly substantial changes in Costikland, some of them visible in the disapearance of links that used to exist on the upper nav bar.

I've accepted a job as "game research engineer," whatever that may be, with NRC, Nokia's research wing. It's a full-time position, unlike my former consulting arrangement with Forum Nokia, which is now at an end. And as part of the deal, I was required to terminate my relationships with Unplugged and the Themis Group, as well as some other minor advisory roles. I still retain some equity in both Unplugged and Themis, but will no longer be officially involved with either.

In addition to deletion of some of the links that used to be along the top of the blog, you'll also note a legal notice at the bottom--essentially pointing out that my opinions are my own, and not those of my employer. In some ways, this will require a bit of a balancing act on my part--it will be impolitic of me to directly challenge the Nokia party line, and certainly I'll be bound by NDAs and such. On the other hand, that was always true--at least the NDA bit, and to a lesser degree the Nokia bit, as I certainly had no interest in biting the hand that fed me (while, to be sure, I had a strong interest in trying to nail some light into heads at Nokia).

You might therefore, a priori, expect to see more discussion of mobile games here; actually, I suspect you may see less, as that may be sensitive, while if I pontificate on MMGs or the interface between game and story, that won't be. Then again, you may, since I'll be spending more of my waking life on mobile stuff.

Now... what, you may reasonably ask, is a "game research engineer"? I can't say I'm entirely certain myself, although I like Alex Macris's image, which is of me in a lab coat taking notes while I watch white rats playing N-Gage. However, as I understand it, it partly involves an evangelist role--keeping in touch with mobile developers and encouraging them to do something more innovative with mobile games (for Nokia devices, of course)--and partly working on experimental game projects, the funding for which must come from Nokia business units, whose support must be solicited--an internal sales issue, as it were.

The "evangelist" role presumably includes writing and speaking at conferences as a Nokia researcher... And thus I suppose I'll have to try to make a distinction between when I am speaking ex cathedra, that is, as a Nokia person, and when it's my own curmudgeonly opinion. Certainly, anything here should be presumed to come from me, not Finland, unless stated otherwise.

On the whole, I expect to be spending the next several months trying to figure out what I'm doing (and what folks at Nokia expect me to be doing)... After which, of course, I'll be trying to figure out how to game the system and accomplish things I'm interested in doing... Such as fostering innovation in mobile games... Which certainly seems to be on Nokia's priority list as well.

We shall see, but I'm pretty chuffed at present.






Comments []

Wednesday, July 14, 2004
Manhattanaddress.com Updated
Finding the location of an address in Manhattan is not as easy as in some cities with a regular street grid; the numbering scheme varies from avenue to avenue. The phone book prints a formula that allows you to find the cross-street for an avenue address, but you have to go looking for the phone book, which is a pain. Also, most of the time what I really want to know is where the closest subway station is.

So, years ago, I did a little Javascript application to both determine the cross street, and also the closest subway. But more, I extended it to cover Broadway and 6th Avenue south of the numbered streets, all the avenues in Upper Manhattan, plus cross-streets, and took at least a stab at locating the nearest subway for streets below 8th (where the grid gets wonky).

It's lived for years at Manhattanadress.com, and I use it a fair bit, and what the hell, pay to host it so others can use it too.

I'm mentioning this, because I recently got around to updating it. Subway lines have changed a bit since 1999, partly because of 9/11 and partly because of repairs to the Manhattan bridge as well as opening of the 61st street connector. In addition, I had known of some errors and omissions--e.g., East End Avenue wasn't listed, I had an East Side IRT station at 72nd (really at 77th), and so on.

In any event, if you hit on it a bit, and find something wrong, please let me know.

Comments []

The MIDP 2.0 Game API, Pt. 3

TiledLayer



Another common practice in games is to use tiles to construct a background. In part, this is a memory-saving technique; if I load one big image to use as the background, it can consume a lot of memory, but if I construct the background out of repeated smaller images, I'm using a lot less space to hold the images from which I construct it. But the tiles can also be used as, in essence, terrain; if one cell of the background contains grass and another pavement, that could affect the movement speed of characters.

TiledLayer is a class that allows you to construct a tiled layer without writing a lot of custom code. The constructor is

TiledLayer(int columns, int rows, Image image, int tileWidth, int tileHeight)

"columns" and "rows" here refer to the size of the tiled layer itself, that is, how many tiles across and down the layer will be. The image includes all of the tiles, just as you include all of the animation frames in a single image when constructing a sprite. And tileWidth and tileHeight are how big each tile will be.

When you construct a TiledLayer, you have to imagine that you're creating two things: an 2D array to hold tile values, and a bunch of tile images. Initially, all array values are 0, meaning no tiles will get painted if you paint at this moment. You need to load the array with the values you want. One way to do so is setCell(int col, int row, int tileIndex); this stores the tileIndex at the indicated column and row in the array. So if you say setCell(0,0,1), the next time the TiledLayer is painted, tile #1 will be painted in the upper left hand corner. (Note that the cells are indexed from 0, even though the tiles are indexed from 1.)

You can also use fillCells(int col, int row, int numCols, int numRows,int tileIndex) to fill a rectangular block of cells with the same tile value.

So far so good, and quite clean. When we come to animated tiles, though TiledLayer looks a little uglier. An animated tile is something like, say, a patch of ocean where we want the waves to undulate over time; two or more animation frames must be displayed in this cell sequentially. TiledLayer allows this, but unfortunately does so in a manner quite different from Sprite, which I find awkward.

To "create" an animated tile, you first use createAnimatedTile(int staticTileIndex). "staticTileIndex" here is one of the tile indexes created when we constructed the TiledLayer; e.g., if we want our animated ocean tile to start with the first image in the TiledLayer, we'd pass "1". The method returns a negative number, which is our "animated tile index"; the first time we create an animated tile, the method returns -1, the second time -2, and so on.

Next, you would use setCell or fillCell to set some cells in the TiledLayer to the animated tile index (e.g., -1). At this point, when the TiledLayer paints, the postive "static" tile associated with this animated tile index will be painted at these cells. In other words, if we do nothing more, we've essentially said that "-1 equals 1", that is, paint tile 1 in every cell indexed by the "animated tile" value of -1.

To actually animate those cells, we have to change the association for -1, and point it to a different (positive) tile index. You do this with setAnimatedTile(int animatedTileIndex,int staticTileIndex). For example, if we want the "-1" tiles to paint tile 2 during the next screen update, we'd invoke setAnimatedTile(-1,2).

This works, but it's not as pretty as Sprite animation. We have to check ourselves to determine when we've come to the end of an animation sequence and therefore should start over with the first frame in the sequence; there is no explicit animation sequence, so we need to write code to construct it ourselves; etc.

IMO, it would have been far cleaner to have an "animated image" class as an extension of Image, with a single system for animating images, then allow both Sprite and TiledLayers to use this same class. But hey, I don't participate in the JSR community process.

Oh yes, there's a final method in the class, setStaticTileSet(Image image,int tileWidth, int tileHeight). This allows you to replace the current tile set for a TiledLayer with a new set of tiles. If the new set contains at least as many tiles as the previous set, the cell array remains unchanged (so that cells that used to paint the 1 tile from the old set now paint the 1 tile from the new set), otherwise the array is reset to 0 throughout.

LayerManager



If TiledLayer is a bit of a kluge, LayerManager is something of a thing of beauty. Any 2D game has to deal with the issue of "layers." Some things get painted on top of other things (e.g., sprites on top of the background). Sometimes all you need is two effective layers, but sometimes you want more.

Once you construct a LayerManager, you can add any Sprite or TiledLayer to it. The LayerManager keeps them in order, that is it knows that one is at the "front," one at the "back," and the order of layers between, known as the "z-order". When it paints, objects closer to the front are painted over objects closer to the back, obscuring one another properly as you'd expect, given layer order.

You can add a layer to LayerManager with append(Layer l), which sticks the passed layer to the back. Or you can use insert(Layer l, int index) to insert the layer at the indicated position (where 0 is the front-most position). Inserting or appending a layer that is already in the LayerManager moves it to the appropriate position, removing it from its old position, because a single instance of a layer can only be in the LayerManager once.

You can also remove(Layer l) a layer, and getLayerAt(int index).

Now here's the beauty part: When you paint() the LayerManager, it automatically paints all Sprites and TiledLayers it contains, at their appropriate positions, in the correct z-order. A single code statement renders your entire screen. This is so simple even I can do it. Nice.

You can also use paint(Graphics g, int x, int y) to paint starting somewhere inside the graphics context, which is useful for clipping. setViewWindow(int x, int y, int width, int height) is most useful when the game world is larger than the device’s display. Imagine that you have a game world that is twice as high and twice as wide as the display. The LayerManager tracks all Layers (sprites, backgrounds, etc.) in the game world, but you only want to paint the part of the world immediately around the player’s character You would use this method to set the view window to the area around the character. In other words, x and y here are not in the graphic context's coordinate system, but in the LayerManagers.

And... That's all. In some ways, MIDP 2.0 has better support for 2D games than any coding environment I've seen (other than special-purpose packages), even if TiledLayer is a bit of a kludge. At some point, perhaps I'll go over the 3D package--but it's MIDP optional, meaning not installed on most phones, so less universally useful than the Game API, which is mandatory for MIDP 2.0-compliant devices.





Comments []

Monday, July 12, 2004
The MIDP 2.0 Game API, Pt. 2

Layer



The Layer class isn't intended to be instantiated itself, but exists as a superclass of Sprite and TiledLayer, and provides some methods that both of those subclasses inherit.

A layer has a position and a size, and when painted, paints at that position. Its position can be determined with getX() and getY(), and its size with getHeight() and getWidth(). Its position can be set to a particular x,y cordinate with setPosition(int x, int y), or moved from its current position with move(int dx, int dy).

Finally, a layer can be visible or "invisible," meaning it is not painted even when its paint() method is invoked. When created, a layer is visible, but this can be changed with setVisible(boolean visible) by passing a value of "false". Visibility status is checked with isVisible().

Sprites



The Sprite class extends Layer, so inherits its methods. There are three Sprite constructors:


Sprite (Image image)
Sprite (Image image, int frameWidth, int frameHeight)
Sprite (Sprite s)


The first is used to create a static, non-animated sprite from a single image. The last copies an existing sprite. The middle constructor is used to create an animated sprite; when it is used, the constructor tries to cut the passed image apart into animation frames of the indicated width and height, numbering frames from zero and starting with the upper left portion of the image, moving to the right, then starting on the next row.

MIDlets are typically designed for devices with severe memory and processing constraints; declaring even a single image requires a certain amount of system overhead. Consquently, it is normal to define animation frames for a game object not by loading a separate image for each frame, but by mushing the frames together in Photoshop or something, and saving it as a single strip. The idea is that you can then pull out a single frame from the strip when you need it. This was common practice in MIDP 1.0 games--what the Sprite class is basically doing is saving you the work of having to write your own code to do this.

You do have to be careful to make sure that your image can be equally divided into frames of the indicated width and height, or the constructor throws an exception.

When an animated sprite is constructed and then painted, the first frame (that is, index 0) is painted. If you want the next frame to be painted the next time paint() is called, you use nextFrame(). Similarly, you can use prevFrame() to go back one frame, or setFrame() to set the sprite to display a particular frame (which are, remember, indexed from zero). Similarly getFrame() returns the current frame index.

Possibly, however, you do not want to display the frames in the order that the constructor extracts them from the image. For instance, perhaps I want to use the order 0, 1, 4, 4 instead. No problem; you use setFrameSequence(int[] sequence). getFrameSequenceLength() returns the number of frames in the current sequence, while getRawFrameCount() tells you how many frames the constructor extracted from the original image.

Using a single image file to extract animation frames is one way MIDP programmers minimize memory use; another is to flip or rotate frames. As an example, when a character moves left-to-right, the animated motion is almost always identical to that used when the character moves right-to-left--except that it's a mirror image. You can reduce memory usage by using the same animation sequence, and just inverting it. (Of course, inversion takes a fair bit of processing power, so you're basically trading off memory for a processing hit--fine most of the time, but a problem if you're doing a whole lot of rotation and flipping).

You can flip and/or rotate a sprite with setTransform(). As a parameter, you pass one of the Field codes defined as part of the Sprite class: TRANS_ROT90, TRANS_ROT180, TRANS_ROT270, TRANS_MIRROR, TRANS_MIRROR_ROT90, and so on. You can restore the sprite to its original state with TRANS_NONE.

One caveat, however; initially, doing this rotates or mirrors the sprite about its upper left pixel (also used for positioning and painting). Thus, if you mirror a sprite and then paint it, it will flip across the screen. If you want it to occupy the same place on the screen, you want instead to mirror it across its center line. You can change what pixel is used for transforms with defineReferencePixel(int x, int y) where the coordinates are in the sprite's own coordinate system (that is, relative to its upper left-hand corner). The current reference pixel can be found with getRefPixelX() and getRefPixelY(). If for some reason you want to set the sprite's position in the graphic context by reference to its reference pixel, instead of the x,y position of its upper left hand corner, you can use setRefPixelPosition() instead of setPosition() or move()--but remember that this method moves the sprite, not the reference pixel's position within the sprite image.

Finally--one thing you need to do in games a lot is determine whether two sprites collide (or a sprite collides with something else). Bullets hit targets, a jumping character misses, things bounce off walls, etc., etc. Previously you had to write your own code for this but the Sprite class provides methods for you. Several, in fact.

collidesWith(Sprite, boolean pixelLevel) determines whether the current sprite has collided with the sprite passed as a parameter. "Has collided with" means "any point of my bounding rectangle intersects any point of the other sprite's bounding rectangle"--unless you pass the boolean "true," meaning "check for pixel level collision." In that case, we first check to see if bounding rectangles intersect. If so, we then check to see whether the collision is only of transparent pixels or not--and if it is, no "pixel level" collision has taken place.

What does that mean? Well, assume my sprite is a spaceship, and we're checking to see whether it hits a sun. All of our original images, as loaded from files, are rectangles. The sun image is a bunch of yellow pixels in the shape of a circle, surrounded by "transparent" pixels that do not paint on top of a background, or on top of other sprites that happen to be nearby. Similarly, my spaceship isn't a solid rectangle, it occupies some part of its rectangular image, and the rest of the image is "transparent." But we only want my ship to die if part of the actual spaceship collides with part of the actual sun. So even if the bounding rectangles of the two images intersect, we say there's no collision unless at least one non-transparent pixel of each image intersects.

Checking on a pixel-by-pixel basis takes more processing power, and should only be used when really necessary.

Other versions of the method are collidesWith(TiledLayer t, boolean pixelLevel)--collisions with the other type of Layer--and collidesWith(Image image, int x, int y, boolean pixelLevel). Regular images, unlike layers, don't inherently have positions; this last version of the method says, in essence, "if this image gets painted at this position, would it be in collision with our sprite?".

Finally, a sprite's collision rectangle is initially cotermininous with its image (or animation frame). You can change the size and location of the rectangle used when checking for collisions with defineCollisionRectangle(int x, int y, int width, int height).

In other words, the Sprite class is quite lovely, because it gives you a simple way to construct, animate, move, and detect collisions for sprites, all things almost any game is going to need to do, and all things that, in MIDP 1.0, required custom code.

To come: TiledLayer and LayerManager.

Comments []

Tuesday, July 06, 2004
The MIDP 2.0 Game API, pt 1.
I've been taking a close look at the MIDP 2.0 Game API recently, and I must say that I like it a great deal. MIDP is "mobile information device profile," and it's the flavor of J2ME (Java 2 Micro Edition) implemented on mobile phones. When the spec for version 1.0 was released, mobile games were an afterthought, and no explicit support for them was provided. Thus, while it's certainly feasible to develop games in MIDP 1.0, it's not exactly easy.

MIDP 2 includes GameCanvas, an extension of Canvas, the class that lets you paint arbitrarily to the phone's screen. GameCanvas has two big advantages; it is inherently double-buffered, and it provides a method, getKeyStates(), that allows easy polling of the device's keys.

Because games update the display frequently, you normally implement a mobile game within Canvas (or, now, GameCanvas). The MIDlet class itself is generally pretty small. Typically, in startApp(), you throw up a splash screen, handle the initial menu navigation, then call GameCanvas's start() method to start actual game play.

Since the main game code is here, you'll create your own GameCanvas, and make it runnable, e.g:

class MyGameCanvas
   extends GameCanvas
   implements Runnable


The base GameCanvas class requires one parameter in its constructor: boolean suppressKeyEvents. Basically, in a normal Canvas, there are two methods used to check for key events: keyPressed() and keyReleased(). If you want to use GameCanvas's own getKeyStates() method, you want to suppress unnecessary calls to keyPressed() and keyReleased(), to avoid unnecessary use of system resources. To suppress those events you'd include true in your "new," e.g.,

GameCanvas myGameCanvas = new GameCanvas(true);

...but if you want to extend GameCanvas, which you do, you instead need to pass the boolean back to the GameCanvas superclass, so you must, as the first line of your constructor, say:

super(true);

...or false, of course, if you don't want to suppress the key events. Failure to include this line will cause an error at compilation, since the compiler won't find a necessary constructor for GameCanvas, and will bitch about this.

getKeyStates()



So how does getKeyStates() work? It returns an integer. One bit is set in the integer for each key that is depressed at the moment that the method is invoked. You don't have to know which bit corresponds to which key, however, since GameCanvas defines static final values that, when ANDed with the integer, will return "true" if that key is pressed. These are named logicalkeyname_PRESSED. E.g., FIRE_PRESSED. So:

int keystates=getKeyStates();
if (keystates & FIRE_PRESSED) {
   //the FIRE key is pressed
...
}


Neat and easy.

The concept of logical keys was introduced with MIDP 1.0. Essentially, somewhere on the device there is guaranteed to be keys that map to UP, DOWN, LEFT, RIGHT, and FIRE. Which keys do so is up to the manufacturer, but they must exist. In some cases, multiple physical keys might map to the same logical key--e.g., both the "2" and pressing up on the D-pad might produce "UP". In general, people are supposed to use logical key mappings, rather than physical keys, in code, because devices do vary in key layout, and you can't be sure that the "2" is always the central topmost key on a device (it's not in a Nokia 3650, for example).

However, you won't always be able to do this in all games. For instance, suppose I want to allow diagonal movement, and I want a "1" to mean "up and left". In this case, I have to rely on physical key presses--which is fine, I can capture them with keyPressed() and keyReleased()--but not, alas, with getKeyStates().

getGraphics() and flushGraphics()



getGraphics() returns a Graphics object for you to paint on; flushGraphics() puts whatever you've painted on it onto the display.

You want to include one call to getGraphics() in your instance of GameCanvas's constructor, not in the main game loop, because each call returns a different instance, e.g.:


public MyGameCanvas() {
   super(true);
   g = getGraphics();
...


...and each time you want to display the next frame of animation, after painting, you simply invoke flushGraphics().

Optionally, you can pass parameters to the method, like this: flushGraphics(int x, int y, int width, int height). The coordinates are relative to your Graphics object's coordinate system, not the display; that is, the pixel at the upper left corner of the device screen will (normally) be the pixel at coordinate x,y in the Graphics after flushing.

The upshot is that you don't have to worry about implementing a double-buffering system yourself.

Layers & LayerManager



The Layer class has two sub-classes: TiledLayer and Sprite, which work about how you'd expect them to, given the names. LayerManager can hold any number of Layers; basically, as you add sprites and tiled layers, you append them to LayerManager. You can move them around, animate them, and so on--and with a single invocation of LayerManager's paint() method, you paint the whole kit and kaboodle, the whole game, to the GameCanvas. This is delightfully simple.

I'll discuss these classes in detail in a later post.

Comments []

Saturday, July 03, 2004
Comrade Radey on Origins
Jack Radey, former head of Peoples Wargames, and whom I Tuckerized years ago as president of Communist America in an alternate history story, writes:

Dana Lombardy had someone send him your review of Origins, and he passed it on to me. Sic transit gloria. Incidently, Origins HAD a program, that featured speakers, controversial panels, etc etc, it was called the War College. For a while it was run by Uncle Lou Zocchi, until close examination discovered that some of the "speakers" only served at his table at the show, while some of the actual speakers couldn't get reimbursed for their travel and hotel... Good old Gulf pirate. I do love Uncle. And I liked his saw playing very much. Professional, my, now you're sounding like Randy Reed... couldn't resist, sorry.
Any rate, Winston Hamilton (the late lamented, of GAMA, GDW, GR/D, the Communist Party, the US Army, and various other things) arranged for, as he put it, my rehabilitation from the Gulag, and I was appointed to head the War College (WC). To guarantee I didn't hijack it and fly it to Cuba, John Hill was appointed as my co-dean (no pun intended). John of course working for an Other Government Agency who dares not speak its name, but hates being referred to as the Cocaine Importing Agency. We worked together splendidly, and we hammered out a deal with GAMA (who, despite what it says in your article) were in charge of Origins all along. Of course they farmed it out to various people, it finally coming to rest with WoTC. The deal was simple. We are not 14 year old dungeon masters. We are retired colonels, active duty colonels, geezer game designers, historians, etc, and in many other venues we would get an honorarium for participating, to say nothing of putting on 4 seminars and participating in a panel. Instead we wanted two things: half a hotel room each (find a room mate or pay half), and our travel expenses to be reimbursed, in cash, at the show. For this they got some forty seminars and three or four panels, and a lot of people who came to the show for no other reason than to hear. Since we ran two seminar rooms in tandem (highest attendence in one was 75 or so, lowest 1, but we averaged 30 or so), many people complained that they couldn't be cloned to go to two at once.
Each year, something went wrong. Each year, a new person was chosen to liase with us, and each year I had to patiently explain that, no, we were not 14 year old dungeon masters... and what the deal was. While Winston lived, he ran interference for us. Often they forgot to mention us in the pre-reg publicity, even though we were often the first to get our schedule in. Every August, John and I, or Rich Muller and I when John had a triple bypass and could no longer do it, would ask for a report on attendence, gate, comments, if they wanted us back next year, what we could do better, feedback, etc. Each year a ringing silence. Then usually in February we would get a phone call, "We need your schedule by Friday to make the pre-reg book." It usually came on a Thursday. Each year we came through, and got rave reviews from both our speakers and the audiences. Our attendence didn't noticeably drop the year they left us out of the program, people told me, "We figured you would be there, so we came."
Meanwhile the show was getting less and less like the old Origins of my youth. But a few familiar faces. Every year I took the opportunity to get loaded with John Prados and Kevin Zucker, which was a joy. But more card players, more live action Vampire, (cool with me, people having fun is fun, and since the Gideons were booked into the convention center right after us, watching the vanguard of the Gideons encounter the rear guard of the live action Vampires was a joy to behold all in and of itself).
Then two years ago we came to a crunch. Hasbro had scratched its corporate head and asked why WoTC was running this show, which bled money in all directions. I mean, why had they paid thousands of dollars for a large blimp suspended from the hall advertising the show? Did they think it brought in ANYONE? Why hire Darth Vader to make radio ads, volunteers told me they had lots of calls asking about the scifi con, and had to patiently explain about it being a gaming con... The idiots running the show were full of that LA PIZAZZ spirit, and hadn't clue number 1 as to who was coming to the show or why. Figured they were all TV addicted idiots.
So the show landed in GAMA's lap. Fine, the first year went OK, although, again, they left us out of the pre-reg booklet, and ignored Rich and my offers to get them a list of history departments, military bases, and wargame contacts to advertise by email our truly stellar line up of presenters. No interest, what kind of dice do you use in your seminars?
The sore point was Colonel David Glantz, founder of the US Army's Foreign Army's Research program, preeminent authority on the history of the Red Army, our biggest draw, and an author with more than a dozen books out. He also self-published a lot, and sold his stuff at his seminars, which went over VERY well with the audiences. But Dave, who lives in Pennsylvania, had an elderly mother with Alzheimers at home, and had to pay to put her in a nursing home for the week, which ain't cheap. We had cut him a special deal, allowing him to have a whole room for himself and his wife. He had also been screwed nearly every year, with the con refusing to pay him what it promised for travel on time, or lousing up his hotel reservation, or something. I would scream and yell and pound the table, suggest breach of contract lawsuits, etc, and eventually he would get what he was owed, but it was a hemorrhoid every year. Although he was tired of the bullshit, he loved the audiences.
I had warned him when I invited him that while some of the audience would be pretty intelligent and well read in the field, there would be a certain amount of ... drooling idiots. "Just like the War College at Carlisle," he replied. When he had given a few, he was in ecstasy, "Seventy five people, who are passionate and well informed on the subject, that ask great questions, and not ONE of them was ordered to be there!"
But the money was getting to be a problem. He had sold most of the books he had to the audiences, and the same people were coming year after year and the revenue was falling, meaning that he was out of pocket for the weekend to provide care for his mom. So he asked for a guarantee of $600. He was our biggest draw and most prestigious name, I agreed to fight for it. Called GAMA. Was told, no, no can do, and in fact the whole deal of paying hotel rooms and travel was also out the window.
So I fought it, as you can imagine. I was reasonable, and I was also quite... ah... undiplomatic. Rich and I decided we were not going to ask our people to go out of pocket to come to Origins. The people I was dealing with seemed to think that a free ticket to the show was a cool deal. The 14 year old dungeon masters go for it, and they sleep four to a room, when they sleep. What's your problem?
The problem was we were the chamber music quartet at the circus. No one knew what to do with us, although everyone was sure we were something very special, whatever we were.
They pointed out our revenue didn't cover our expenses (our budget had been $6,000 for 12 years in a row). We pointed out that based on polling our attendees we could easily charge more for the WC, more like $25 per than $15 (for 20 seminars and three or four panels with prestigious speakers that's one fuck of a bargain). They responded the previous year by dropping the price to $10. We suggested trying to bring in more people by publicizing it to the history departments and military. They responded by suggesting they have a girl in a skimpy bathing suit cut a ribbon opening the show. They were very concerned with a logo, with getting some great name, like Tom Clancy, or Schwarzkopf (Schwarznegger?), we suggested just putting out our schedule, with its really exceptional list of topics and speakers would actually draw the people who would come to the show for just that.
No dice, and we were told to fuck off, and not very politely. So Pete Panzeri put on a War College lite, including speakers that John Hill and I had blackballed (the McDonut Brothers, USN) for really having not much to say, and some other crap, whoever would work for substandard conditions. I agreed not to stand in their way, and forwarded Pete's invite to my "faculty." A few friends of mine, who needed to go to Origins anyway for business went ahead and presented (the word that comes to mind is scabbing but... let it go.)
Never got a thank you, job well done, how can we work together to make this happen better, any way you can reduce your budget? Nah, just fuck off.
So for the last two years I have not been at Origins. Can't afford to go, and can't think of any reason to justify it. I am living here in Eugene, Oregon, happily remarried, raising chickens and occaisional hell, singing, painting miniatures, making forays into political activism now and again, and watching the planet slide into the dumper. Although W may precede it by some margin, the future looks worse to me than it ever has, I suspect the race has run its course. If I'm lucky, I'll be gone by the time the roof falls in, my poor kids, and everyone elses.
Peace,
Jack Radey


Comments []


This page is powered by 

Blogger. Isn't yours?