NokiaAs you saw in Chapter 3, Nokia has an impressive range of J2ME-compatible MIDs on the market. In fact, they provide a significant portion of the installed user base you'll target with your games. Fortunately, Nokia has backed their J2ME hardware with solid support for Java developersespecially game developers. Using the Nokia SDKs, you will be able to add the following features to your games:
Nokia provides two distinct tools to help you develop J2ME games for their range of MIDsthe NDS (Nokia Developer's Suite) for J2ME and Java SDKs for individual devices. You'll download and install both of these in the next section. Installing the ToolsThe NDS provides a basic Nokia phone emulator, audio tools, and a simple development environment somewhat similar to Sun's J2ME Wireless Toolkit. (Actually, the NDS is a little better in some ways.) I'm not going to go into detail about using the NDS as an IDE, but feel free to play around with it. Although it's pretty cute, it still doesn't serve as a fullscale IDE. However, the NDS is a great way to manage and run the various Nokia emulators (in the form of phone SDKs). It also has support for JBuilder and Sun ONE Studio integration, so download it from the Nokia Web site and let's take a tour. To download the NDS, you need to visit Forum Nokia at http://www.forum.nokia.com. Navigate to the Java section and then to the Tools and SDKs page. (There's a ton of cool stuff around there, so don't get distracted now!) What you're after is the Nokia Developer's Suite for J2ME. You need to become a member of the developer forum (free of charge) to download these tools. As you can see in Figure 6.1, after you download the NDS, install it as you normally would and select the IDE integration you prefer. Figure 6.1. The Nokia Developers Suite 2.0 installation options for integration with an existing development environment.
Next, set the installation directory to be under your J2ME working directory like that in Figure 6.2 (feel free to use your own directory location). Nokia 175 Figure 6.2. Select the directory in which to install the NDS.
The Nokia UI APINokia makes additional MIDP features available through the Nokia UI API. You will find a version of the API, in the form of documentation and class files (classes.zip), packaged with most of the device SDKs (such as C:\J2ME\Nokia\Devices\Nokia_7210_MIDP_SDK_v1_0) Each device comes packaged with a complete copy of the Nokia UI, however they are all the same thing. Life on planet Nokia consists of the classes and interfaces highlighted in Figure 6.3. Figure 6.3. The Nokia UI classes
To see a Nokia emulator in action you can just run the executable (such as 7210.exe) found in the bin directory for each device (C:\J2ME\Nokia\Devices\Nokia_7210_MIDP_ SDK_v1_0\bin) and then use the file menu to open a JAR file containing your classes. NOTE
Tip I would stay away from using the NDS to build your project; use your IDE or the command line to compile the project and then use the NDS device emulators to view it.Also, you only need to include the Nokia UI specific classes (classes.zip) on your classpath if you're using Nokia UI features. To get things working, you need to add lib/classes.zip to your build class path. Then you will be able to use the classes in the com.nokia.mid.* package. Take a look at the details of these features. Device ControlYou can control some of the extra physical features of Nokia devices using the com.nokia.mid.ui.DeviceControl class. (Table 6.1 shows the methods available.)
The two device elements you can control are the lights and vibration. To temporarily flash the lights on and off, use the flashLights method. For example: import com.nokia.mid.ui.DeviceControl; … DeviceControl.flashLights(5000); This code will cause the lights (such as the LEDs) to flash. If there is no support for this feature, then nothing will happen (duh). The integer value specifies the length of time (in milliseconds) to keep the lights on, although the device might override this value if you specify a number that is too large. The other method relating to lights, setLights, allows you to control any of the lights on the device individually, such as the backlight or the LEDs …in theory. In reality, Nokia only gives you the ability to control the device's backlight (if it has one). To do this, call the method with the light number (the first integer) set to 0 for the backlight and the level integer set to a number between 0 and 100 (where 0 is off and 100 is the brightest level). For devices that don't support graded backlighting, all values between 1 and 100 just translate to on. Here's an example of setLights in action: DeviceControl.setLights(0, 100); NOTE
Tip A word of warning about playing with the backlight: There is no method to determine the current backlighting level; therefore, you have no way of restoring the lighting to the level the user previously had. Be careful using this function. A user won't be impressed if, while playing in a dark place, you turn the lights out to reward them for completing a level. If you really want to add this function to your game, consider making it an option the player can enable or disable. This applies to the vibration function as well. Controlling the vibration of the phone is one of those cool things you really want to do. Crashing into a side railing or taking shield damage feels much cooler if you give the phone a jiggle when it happens. To start the phone vibrating, use DeviceControl.startVibra(10, 1000); The first parameter, the frequency, is an integer in the range of 0 to 100. This number represents how violent the shaking should be. The second integer is the duration of the vibration in milliseconds. You can vary these numbers depending on what's happening in the game. For example, you might make the phone vibrate more violently for bigger shield hits or for a longer duration as the shield weakens. A call to startVibra will return immediately, regardless of the duration of the call. If the device does not support vibration it will throw an IllegalStateException. This can happen even on a device that supports vibration if, for example, it's docked in a cradle. To immediately stop any current vibration, use the stopVibra method. SoundThe Nokia UI also includes the ability to play sounds on compatible MIDs (Table 6.2 lists the available methods). The most basic support you can rely on is playing simple tones as well as Nokia ring-tone format (RTPL) tunes. More advanced MIDs can also play digitized audio using the WAV format.
Playing TonesThe first thing to playing sounds is determining the support for the device. The getSupportedFormats method will return an array of integers containing either FORMAT_WAV or FORMAT_TONE. You can create a simple tone sound and play it using Sound s = new Sound(440, 1000L); s.play(1); This code constructs a 440-Hz tone with a duration of 1000 milliseconds. The play method starts the sound playing and immediately returns; your MIDlet continues with the sounds playing in the background. The sound will stop either when the duration expires or when you ask it to stop by calling the stop method. NOTE
Tip All Nokia MIDs at least support frequencies of 400 Hz and 3951 Hz. You should consult the Nokia UI API javadoc for details on the other supported frequencies. Some MIDs support multiple sounds played simultaneously. You can determine how many sounds are supported using the getConcurrentSoundCount method. If a call to play a sound exceeds the maximum number supported, the last sound playing will stop and the new sound will start. You can also use the API to play simple tunes, such as the ring tones included with the phone. This functionality is available through Nokia's RTPL (Ring Tone Programming Language), which is part of the SMS (Smart Messaging Specification). The simplest way to generate RTPL music is to use the Nokia PC suite software. NOTE
Tip Support for more advanced multimedia (including video) on newer MIDs is available through Sun's MMAPI (Mobile Media API). For more information, visit http://java.sun.com/products/mmapi. Listening InWhen you're playing sounds, it isn't always easy to determine when a particular sound or RTPL tune has finished playing. For simple sounds (such as weapon fire) you probably don't care, but for background tunes or level-win rewards you need to know when the sound (or song) has finished, by either moving on in the game or starting another song. To determine when a sound or song has ended you use the SoundListener interface. To set up a listener, create a class (or use your existing MIDlet) that implements the SoundListener interface, and then implement the soundStateChanged method. For example: import com.nokia.mid.sound.Sound; import com.nokia.mid.sound.SoundListener; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; import javax.microedition.lcdui.*; /** * A demonstration of the Nokia UI Sound API. * @author Martin J. Wells */ public class NokiaSound extends MIDlet implements CommandListener, SoundListener { private Sound sound; private Form form; private Command quit; private Command play; /** * MIDlet constructor creates a form and appends two commands. It then * instantiates a Nokia Sound class ready to play sounds for us in response * to the play command (see the commandAction method). */ public NokiaSound() { form = new Form("Nokia Sound"); form.setCommandListener(this); play = new Command("Play", Command.SCREEN, 1); form.addCommand(play); quit = new Command("Quit", Command.EXIT, 2); form.addCommand(quit); // Initialize a sound object we'll use later. We create the object here // with meaningless values becuase we'll init it later with proper // numbers. A sound listener is then set. sound = new Sound(0, 1L); sound.setSoundListener(this); } /** * The Nokia SoundListener interface method which is called when a sound * changes state. * @param sound The sound that changed state * @param state The state it changed to */ public void soundStateChanged(Sound sound, int state) { if (state == Sound.SOUND_UNINITIALIZED) form.append("Sound uninitialized "); if (state == Sound.SOUND_PLAYING) form.append("Sound playing "); if (state == Sound.SOUND_STOPPED) form.append("Sound stopped "); } /** * Called by the Application Manager when the MIDlet is starting or resuming * after being paused. In this example it acquires the current Display object * and uses it to set the Form object created in the MIDlet constructor as * the active Screen to display. * @throws MIDletStateChangeException */ protected void startApp() throws MIDletStateChangeException { Display.getDisplay(this).setCurrent(form); } /** * Called by the MID's Application Manager to pause the MIDlet. A good * example of this is when the user receives an incoming phone call whilst * playing your game. When they're done the Application Manager will call * startApp to resume. For this example we don't need to do anything. */ protected void pauseApp() { } /** * Called by the MID's Application Manager when the MIDlet is about to * be destroyed (removed from memory). You should take this as an opportunity * to clear up any resources and save the game. For this example we don't * need to do anything. * @param unconditional if false you have the option of throwing a * MIDletStateChangeException to abort the destruction process. * @throws javax.microedition.midlet.MIDletStateChangeException */ protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { } /** * The CommandListener interface method called when the user executes a * Command, in this case it can only be the quit command we created in the * constructor and added to the Form. * @param command the command that was triggered * @param displayable the displayable on which the event occurred */ public void commandAction(Command command, Displayable displayable) { // check for our quit command and act accordingly try { if (command == play) { // initliaze the parameters of the sound (440Hz playing for 2 // seconds - 2000 ms) sound.init(440, 2000L); sound.play(1); } if (command == quit) { destroyApp(true); // tell the Application Manager we're exiting notifyDestroyed(); } } // we catch this even though there's no chance it will be thrown // since we called destroyApp with unconditional set to true. catch (MIDletStateChangeException me) { } } } NOTE
Tip The Nokia NDS contains a great sound playing example in the C:\j2me\Nokia\Tools\Nokia_ Developers_Suite_for_J2ME\midp_examples\Tones directory. NOTE
Tip You can control the playback volume using the Sound.setGain method with a number ranging from 0 to 255. Full-Screen DrawingEven when you are using the MIDP low-level UI's Canvas class, you still don't get access to the entire screen. This is inconvenient because of the relatively significant space you lose at the top and bottom of the screen that could be put to good use. (For example, you could use the space to display the number of lives the player has left.) As you can see in Figure 6.4, Nokia's FullCanvas is an extension of the MIDP Canvas class that provides full-screen access. You can see the lone method for the class in Table 6.3. Figure 6.4. Nokia's FullCanvas class gives you access to the entire display.
The price you pay is the loss of the command system. You need to implement this yourself using keystroke responses. (The addCommand and setCommandListener methods will throw an IllegalStateException.) In exchange, you will gain access to the keys previously reserved for commands, as follows:
In the following example, you will draw some random-colored rectangles extending out as far as the canvas will allow. As you can see from the results in Figure 6.1, you can gain a sizeable amount of rendering space when you use FullCanvas. import com.nokia.mid.ui.FullCanvas; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import java.util.Random; /** * An example that demonstrates the use of the Nokia UI FullCanvas class. * You must have the Nokia UI class files on your classpath and run this example * on a Nokia emulator (such as the 7210). * @author Martin J. Wells */ public class FullCanvasTest extends MIDlet { private MyCanvas myCanvas; /** * An inner class which extends from the com.nokia.mid.ui.FullCanvas and * implements a random rectangle drawer. */ class MyCanvas extends FullCanvas { private Random randomizer = new Random(); /** * A method to obtain a random number between a miniumum and maximum * range. * @param min The minimum number of the range * @param max The maximum number of the range * @return */ private int getRand(int min, int max) { int r = Math.abs(randomizer.nextInt()); return (r % (max - min)) + min; } /** * Canvas class paint implementation where we draw the some random * rectangles. * @param graphics The graphics context to draw to */ protected void paint(Graphics graphics) { for (int i = 10; i > 0; i--) { graphics.setColor(getRand(1, 254), getRand(1, 254), getRand(1, 254)); graphics.fillRect(0, 0, i * (getWidth() / 10), i * (getHeight() / 10)); } } } /** * MIDlet class which constructs an instance of our custom FullCanvas. */ public FullCanvasTest() { myCanvas = new MyCanvas(); } /** * Called by the Application Manager when the MIDlet is starting or resuming * after being paused. In this example it acquires the current Display object * and uses it to set the Canvas object created in the MIDlet constructor as * the active Screen to display. * @throws MIDletStateChangeException */ protected void startApp() throws MIDletStateChangeException { Display.getDisplay(this).setCurrent(myCanvas); } /** * Called by the MID's Application Manager to pause the MIDlet. A good * example of this is when the user receives an incoming phone call whilst * playing your game. When they're done the Application Manager will call * startApp to resume. For this example we don't need to do anything. */ protected void pauseApp() { } /** * Called by the MID's Application Manager when the MIDlet is about to * be destroyed (removed from memory). You should take this as an opportunity * to clear up any resources and save the game. For this example we don't * need to do anything. * @param unconditional if false you have the option of throwing a * MIDletStateChangeException to abort the destruction process. * @throws MIDletStateChangeException */ protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { } } Direct GraphicsNow you get to the real fun of the Nokia UIgraphics. And let me tell you, what you get is downright cooltransparency, image rotation, triangle and polygon drawing, and monkeys …yeah, monkeys …little pink, fluffy monkeys will jump out of the device and dance naked for you! Um, all right. There are no monkeys, but seriously, this is where the Nokia UI really comes into its own. Features such as image rotation and transparency are more than just a little bit important for making great games. These graphics functions are available through the com.nokia.mid.ui.DirectGraphics class (the full API is listed in Table 6.5), which you acquire using the com.nokia.mid.ui. DirectUtils.getDirectGraphics static method (the DirectUtils API is shown in Table 6.6). For example:
protected void paint(Graphics graphics) { // Get the Nokia DirectGraphics object DirectGraphics dg = DirectUtils.getDirectGraphics(graphics); // Call DirectGraphics methods … } The DirectGraphics object returned in the method call in this example is not the same object as the original Graphics context, nor is DirectGraphics a super of Graphics.However, the two contexts are inherently connected; changes to one will affect the other. This is not only important, it also makes for some great bonus features when you combine the two. Once you have a DirectGraphics object, any call on the original Graphics object's method to change color (setColor or setGrayScale), clipping (setClip or clipRect), stroke (setStrokeStyle), or translation (translate) will affect the output of any future drawing on the linked DirectGraphics object. Keep in mind that at this point I'm not saying you call these methods on the DirectGraphics object itself. (They aren't there anyway.) You call them on the original Graphics object. The link between the two will result in the change affecting the DirectGraphics object as well. Keep this in mind because I'll get to an example soon. On the flip side, the DirectGraphics object's setARGBColor method will change the current rendering color on the original Graphics context. What's cool about that? Notice the "A" in the color method call? Yep, that's an alpha channel! You can set a variable level of trans-parency when drawing graphics. Not only can you do it using the DirectGraphics calls, but you can also do it using the Graphics call. For example, in the following code you'll draw two rectangles using Graphics and DirectGraphics to set an alpha channel color. Both rectangles will have a high level of transparency. protected void paint(Graphics graphics) { // Get the Nokia DirectGraphics object DirectGraphics dg = DirectUtils.getDirectGraphics(graphics); // Draw a transparent rectangle in the center of the screen dg.setARGBColor(0xFFFF0000); graphics.fillRect(50, 50, getWidth()-100, getHeight()-100); // Draw a rectangle that fills the screen (no transparency) dg.setARGBColor(0xFF0000FF); graphics.fillRect(0, 0, getWidth(), getHeight()); } The call to setARGBColor uses a four-byte integer value (Quad 8) to represent the alpha-, red-, green-, and blue-channel intensity. For the alpha channel, 00 represents completely transparent and FF (or 255) is fully opaque. Table 6.4 lists a few examples.
The code example you just saw will draw a big blue rectangle. Because the last setARGBColor is fully opaque, none of the underlying smaller red rectangle will show through. If you adjusted the color of the rectangle to have an alpha level of 55 (0x550000FF), you would see a mixture of both colors displayed. If the alpha level were further dampened down to 00 (0x000000FF), only the underlying red rectangle would show. Figure 6.5 shows an example of all three results. Figure 6.5. Three examples of drawing using Nokia alpha channels.
Notice the transitional color of the red rectangle. In the second image, you see a blending of both rectangles on the display at the same time. Remember this trick; it's one of the coolest you've got. Triangles and PolygonsMIDP limits geometric drawing to just lines and rectangles. Although you can use lines to draw almost any shape, there's no way to fill the compound shapeand it's about as much fun as watching paint dry. The Nokia UI adds the capability to draw outlined or filled shapes with three or more sides (triangles and polygons). The method calls to draw geometry are relatively simple so an example should give you the general idea. The following code draws a triangle and a rectangle. protected void paint(Graphics graphics) { // Get the Nokia DirectGraphics object DirectGraphics dg = DirectUtils.getDirectGraphics(graphics); // Draw a colored triangle dg.fillTriangle(getWidth() / 2, 10, getWidth(), getHeight(), 0, getHeight(), 0xFF00FF00); // Poly wants a display int[] xPoints = {25, 50, 25, 10}; int[] yPoints = {10, 25, 50, 25}; dg.drawPolygon(xPoints, 0, yPoints, 0, xPoints.length, 0x55FF0000); } Reflecting and RotatingDirectGraphics provides an enhanced drawImage method capable of rendering an image using rotation and reflection. Given that graphics are commonly drawn in multiple directions in a game (such as to represent a sprite moving from left and right), you'll find that without reflection or rotation multiple versions (sometimes quite a few) of the same images are required to represent these different directions. A combination of reflection and rotation lets you reuse the same graphics, thus freeing up space in your JAR for yet more graphics. To render an image using reflection or rotation, use the DirectGraphics.drawImage method. Table 6.7 lists the available options. You can combine flipping and rotating.
In the following code, you will use drawImage to render an image both rotated and reflected. class MyCanvas extends FullCanvas { private Image alienHeadImage = null; /** * A custom FullCanvas that loads up a sample image (make sure the * image file is in the JAR file!) */ public MyCanvas() { try { alienHeadImage = Image.createImage("/alienhead.png"); } catch (IOException ioe) { System.out.println("unable to load image"); } } /** * The overriden Canvas paint method draws the previously loaded image * onto the canvas using the passed in graphics context. */ protected void paint(Graphics graphics) { // Get the Nokia DirectGraphics object DirectGraphics dg = DirectUtils.getDirectGraphics(graphics); // Draw the alien head rotated by 270 degrees dg.drawImage(alienHeadImage, getWidth()/2, getHeight()/2, Graphics.HCENTER | Graphics.VCENTER, DirectGraphics.ROTATE_270); // Draw the alien head upside down dg.drawImage(alienHeadImage, getWidth() / 2, (getHeight() / 2) + 20, Graphics.HCENTER | Graphics.VCENTER, DirectGraphics.FLIP_VERTICAL); } } Image TransparencyAt the start of this section on DirectGraphics, you saw how you could set transparency, or alpha channels, for graphics rendering. Well here's a news flash: This also extends to the PNG image format! Yep, that's right; you can render images using the full alpha channel in PNG files exported directly from your favorite graphics application. The most significant use of this feature in a game is when you render a non-rectangular image onto a background. Without transparency support, you cannot draw the image seamlessly over a background imageyou'll have ugly bordering. In Figure 6.6, you can see a good example of an image without transparency (top) and with transparency (bottom). Figure 6.6. An example drawing a transparent and non-transparent PNG image.
The following code renders these images. As you can see, there is basically no difference when you load or display a transparent image. protected void paint(Graphics graphics) { // Get the Nokia DirectGraphics object DirectGraphics dg = DirectUtils.getDirectGraphics(graphics); // Fill the screen with pastel green graphics.setColor(50, 200, 50); graphics.fillRect(0, 0, getWidth(), getHeight()); // Draw the alien head dg.drawImage(alienHeadImage, getWidth()/2, getHeight()/2, Graphics.HCENTER | Graphics.VCENTER, 0); // Draw the transparent alien head. // Note that I'm assuming you've previously loaded a transparent // image file into an Image object named transAlienHeadImage. dg.drawImage(transAlienHeadImage, getWidth() / 2, (getHeight() / 2) + 20, Graphics.HCENTER | Graphics.VCENTER, 0); } Pixel Data AccessLast but not least, DirectGraphics gives you access to the nuts and bolts of the native pixel data with which a Nokia MID stores images. You can extract pixel data, modify it, and then render your modified version. Uses of direct pixel manipulation are pretty much endless because this function allows your program to modify the image. For example, you can turn one range of colored pixels into another color (or level of transparency). What's good about that? Well imagine the case of a space shooter. At the start of the game you could let the player choose his ship color, and then use pixel manipulation to change one color (such as the go-fast stripes along the wings) into another color of his choice. This could even include different levels of depth that match the original paint job. This wouldn't normally be practical because of the extra space different-colored ships would take up in the JAR file. This all sounds great, but unfortunately it's not that easy to use. First, different Nokia models store image data internally in different formats. (Table 6.8 lists the various formats.) You need to determine the native storage format using DirectGraphics.getNativePixelFormat. Once you have the format, you can get the image data in the form of an array of integers, shorts, or bytes, depending on the native image format you're requesting. For example, the Nokia 7210 (which you'll use for all the following examples) has a native pixel format of TYPE_USHORT_444_RGB. Quite a mouthful, huh? This format uses the Java short type to store pixel data. With all this in mind, take a look at the code to grab the pixel data.
int pixFormat = d g.getNativePixelFormat(); // make sure we have a pixel format we know how to handle properly if (pixFormat == DirectGraphics.TYPE_USHORT_444_RGB) { // Create an array large enough to hold the pixels we grab (20 x 50) short pixels[] = new short[20*50]; dg.getPixels(pixels, 0, 20, 0, 0, 20, 50, DirectGraphics.TYPE_USHORT_444_RGB); ... The first thing you do in this code is to determine the pixel format of the data you're going to grab. To keep things simple, I've just hard-coded a test to make sure the format is what you expect (TYPE_USHORT_444_RGB). Adding support for other formats is a relatively painless process, but it doesn't make for concise examples at this stage. Before I move on, I want to talk about exactly what the TYPE_USHORT_444_RGB format means. First, the USHORT indicates that a Java short type (2 bytes, or 16 bits) represents each pixel in the image. To understand all this, you can think of a short as being four sets of 4 bits, commonly written as 4444. As you can see in Figure 6.7, the 444_RGBaka 12-bit colorindicates 4 bits for each of the red, green, and blue components of the color. Now because 4 bits can only represent 16 possible combinations, this means there is a maximum of 16 reds, 16 greens, and 16 blues. If you combine all these, you have a maximum of 4096 (16 * 16 * 16) colors on the device. This is the capability of the 7210 display; other MIDs have different capabilities. Figure 6.7. The byte layout for a 444_RGB color pixel.
One thing you might notice here: Since the Java short type has 2 bytes (16 bits) and the combination of the RGB components is only 12 bits (4 + 4 + 4), what is the purpose of the other 4 bits? The answer is nothingthis format doesn't require them. All right, now that you have a little background on the 444_RGB format, take a look at things in action. The following code draws an image with a go-fast red stripe down the center. Using the Nokia UI direct pixel access, you're going to change just the red components of the image to green. A free paint job that doesn't waste any precious JAR space! protected void paint(Graphics graphics) { // Get the Nokia DirectGraphics object DirectGraphics dg = DirectUtils.getDirectGraphics(graphics); // Paint the entire canvas black graphics.setColor(0, 0, 0); graphics.fillRect(0, 0, getWidth(), getHeight()); // Draw the original red-striped image dg.drawImage(redStripeImage, 0, 0, Graphics.TOP|Graphics.LEFT, 0); // Get the native pixel format int pixFormat = dg.getNativePixelFormat(); // Make sure we have a pixel format we know how to handle properly if (pixFormat == DirectGraphics.TYPE_USHORT_444_RGB) { int imgWidth = redStripeImage.getWidth(); int imgHeight = redStripeImage.getHeight(); // Create an array big enough to hold the pixels short pixels[] = new short[imgWidth*imgHeight]; // Grab them from the graphics context in the array dg.getPixels(pixels, 0, imgWidth, 0, 0, imgWidth, imgHeight, DirectGraphics.TYPE_USHORT_444_RGB); // Loop through the contents of the array looking for the right color for (int y=0; y < imgHeight; y++) { for (int x = 0; x < imgWidth; x++) { // Pixels are stored in one long array so we need to get an // index relative to our x and y int a = (y * imgWidth) + x; short pixel = pixels[a]; // Check to see if the pixel has all the RED bits on if (pixel == 0x0F00) pixels[a] = (short)0x00F0; // } } dg.drawPixels(pixels, false, 0, imgWidth, 60, 0, imgWidth, imgHeight, 0, DirectGraphics.TYPE_USHORT_444_RGB); } } NOTE
Tip You can see a complete working example of this on the CD in the Chapter 6 source code directory under the name NokiaGraphicsTest. You can recreate this example yourself by creating a PNG image file with some of the pixels set to maximum solid red. The preceding code will loop through the image and replace any pixels matching this color with solid green pixels. You can see the results in Figure 6.8. Figure 6.8. The results of turning all the red pixels to green using RGB pixel manipulation.
This is a simple example of pixel manipulation directly replacing one pixel with another. Using other techniques, the sky really is the limit to what you can do. For now, here are some ideas of what's possible:
NOTE
Tip One thing to keep in mind: Modifying pixels is a relatively slow process, so avoid (as much as possible) doing it on every call to paint. If the effect you're using still works, you can just pre-render modified versions (such as the alternative player-colored ships) as new images and then display those during the paint method. This has very little cost in terms of rendering speed. Some effects require that you check pixels on every paint pass (such as the dynamic lighting explosions), but doing too much of this will kill the MID's CPU in no time. Give the effect a try (on the real phone) to see how it works. And above all, make these effects optional extras that the player can disable. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||