Now that we are up to speed on how to get input from the keyboard we can turn our attention to the mouse. The mouse is just as easy as the keyboard to use, but we have more methods at our disposal when we deal with the mouse.
The following are the methods that we can use for the mouse:
public boolean mouseEnter(Event e, int x, int y)
public boolean mouseExit(Event e, int x, int y)
public boolean mouseDown(Event e, int x, int y)
public boolean mouseUp(Event e, int x, int y)
public boolean mouseMove(Event e, int x, int y)
public boolean mouseDrag(Event e, int x, int y)
One important point to note here is that for each of the above methods you can simply return true.
All of the methods for the mouse are fairly clear and understandable. You will notice that each method receives the x and y coordinates of the mouse location with respect to the upper left-hand corner of the applet window.
The mouseEnter and mouseExit methods are called automatically when the mouse enters or exits the applet area. The mouseDown and mouseUp methods are called when a mouse button is pressed or released. The mouseMove method is called when the mouse is moved in the applet area. The final method, mouseDrag, is called when the mouse is moved in the applet area while a mouse button is pressed.
The best way to test out these different mouse methods is to set up a basic applet that prints a string out to the screen. The string should be changed by the various mouse methods to reflect which method was called and the current x, y location of the mouse.
You should now have a fairly good idea about what it takes to work with input from the keyboard and mouse in java applets. It is now time to go back into the realm of output.
We have been focusing on the input and looping phases of our game so far, but now it is time to get back into looking at the output phase. Double buffering is a technique that will be familiar to anyone who has done much in the way of game programming. A common problem that you will notice as soon as you make an applet with threads is that you get an annoying flicker. This flicker comes from the fact that we are drawing bits and pieces of our game when we should be drawing all of our game at once.
The trick to fixing the flicker problem is actually very simple. The first step that we want to take is to setup a couple of variables.
The variable offscreenImage is going to be our backbuffer where we will draw what will be appearing next on screen. We need the variable offscr to hold the graphics context of our backbuffer so that we can later draw a variety of stuff into it just like we normally would in our paint method.
width = size().width;
height = size().height;
offscreenImage = createImage(width, height);
offscr = offscreenImage.getGraphics();
The next important step that we need to take is to setup our backbuffer so that we actually have some space to draw into and then we need to get its graphics context which we will store in offscr. You will notice that I use width and height to store the dimensions of my applet space and that I then use these values to create a backbuffer that is the same size. In order to get the graphics context of this backbuffer I simply have to call the method getGraphics in my variable offscreenImage and we are set. One other note that you need to watch out for is that these four lines of code should be placed inside the init method so that they are executed when the applet first starts up.
offscr.fillRect(0, 0, width, height);
g.drawImage(offscreenImage, 0, 0, this);
This next segment of code is an example of what will be in your paint method. The first step is to clear the backbuffer. We do this by simply creating a filled rectangle that has the dimensions of our applet space. Take care to note that rather than using the usual graphics context g we are instead using our backbuffer which is represented by offscr. You can clear the backbuffer to any color that you want, but I decided here that black was a good color. We can then proceed to draw any shapes or text that we would like to display while making sure that we use offscr rather than g. Once we are ready to flip our buffers and display what is in the backbuffer on the screen we then use g.drawImage to display the buffer on the screen. This way we draw everything at once. If you don't remember what the parameters of the drawImage method stand for make sure you check back to part one of this series.
public void update(Graphics g)
This piece of code that we need to add to our program to get double buffering working is the update method. Normally the update method clears the screen and then paints, but this will again lead to flicker so we need to make sure that all it does is paint. If you try writing a program with the above code, but leave out the update method change that I have shown you will still see flicker and all your work will be for naught so take care.
Coding the Game
So now that we have a basic idea of how to use threads, double buffering, and the mouse and keyboard we can start working on an air hockey game. I will cover what needs to be coded and how we could go about doing this here and you can look at the sample code which is available on my web site (www.cpsc.ucalgary.ca/~kinga). The code may look kind of confusing, but in the next article I will talk about how to use additional class and methods to make our code look more organized and easier to read.
First off we are going to need to accept input so that the players can move their paddles up and down. The easiest way to accomplish this is going to be to set up a keyDown function with 4 if statements that test the key pressed against the up and down keys for the two players. These if statements will end up changing the value in a variable holding the y position of the appropriate paddle. This y position will be used for the drawing of the paddle and for collision tests to ensure that the paddle doesn't go off the edge of the board.
If you want to expand the game a bit an extra that you could could add in here would be allowing the paddles to move left and right as well as up and down. This means that you will have to add some extra collision detection code in to check the paddles against the edges of the board and the other paddle. You could also try adding in some mouse support for your game. This could include the ability to use the mouse to move one of the paddles around or you could use the mouse to create some sort of menu system.
The third step that we need to cover is the output on the screen. Now you are probably saying what happened to step number two, but don't worry it is coming shortly. The output that we need for the screen is very, very simple. All we really need are two filled rectangles for the paddles, an oval for the puck, and some text to display the score. If we wanted to we could make things more complicated by having a nice background for the game to be played on (an image or something dynamic like a starfield), having a puck whose color is constantly changing, or even obstacles on the board (this could get complicated so don't start off trying to do this).
The second phase which I talked about at the very beginning is the processing stage. So what needs to be processed in our game and what variables will we most likely need? The goal of the game is to have a puck bouncing back and forth on the board and it should bounce off the walls and the paddles. First off we are going to need to have the coordinates of the puck and the two paddles. This means that we will need to have an integer x,y value for each of them. Next we are going to need to have a speed value for the puck. The speed values will be integers which represent the change in the x and y postion of the puck in each time step. With this much information we can move the puck in our run method and test the positions of the puck and paddles to see if there is a collision.
This brings up a good point: how do we detect collisions. There are a lot of complicated formulas for checking collision, but there are some simple methods that will be sufficient for us.
For our purposes I'm going to assume that the board is the size of the entire applet area. This means that the extents of the board are from 0 to size().width and 0 to size().height. You will notice that here that I have used size().width and size().height here to represent the maximum height and width of the applet space. If you use these exactly as I have written here in your programs they will provide you with the values that you desire. Testing the paddle for collision with the edge of the board is as simple as having if statements to check whether or not the y value of the paddle is greater than the maximum height or less then zero. If you allow the paddle to move left and right then you can check to see if the x value of the paddle is less then 0 or greater than the maximum width. If we discover that there is a collision or we have gone over the edge of the board then all we have to do is move the paddle back onto the board (eg. 0 or max. height - paddle size).
We only need to worry about paddle to paddle collision if we allow the user to move his paddle to the left and right. You could even avoid having to worry about this type of collision if you said that a player couldn't move his paddle past the center of the board. Another option would be to let the paddles simply pass through each other with no collision at all. I would recommend one of the above options since the number of times the two paddles are going to get close enough for a collision is almost zero. If you are still interested in pursuing this type of collision then I would recommend that you follow the process that I will discuss in a moment for paddle and puck collisions. This method involves taking the x and y position of the paddle and comparing it against the four edges of the other paddle to see if it is inside the other paddle. If a collision has occurred then we want to move it out of collision.
Checking for collisions between a paddle and the puck isn't easy, but it is a very important part of the game. You could try to get fancy here, but I recommend that you keep it simple. I believe that the easiest way to check for a collision is to check the x and y coordinates of the puck against the four edges of the paddle. The four edges of the paddle are: paddlex, paddlex + paddle_width, paddley, and paddley + paddle_height. With this knowledge in hand it is a simple matter of creating an if statement for each paddle which will consist of four parts testing the four aforementioned edges against the x and y of the puck to see if the puck is inside the paddle. If it is unclear what is going on I would recommend drawing a little diagram of what the situation looks like and this should make things easier for you. Once we have discovered that a collision has occurred then we need to come up with some means of resolving the collision. The best way to do this is to take the speed of the puck in the x direction and negate it. By negating it we get very believable bouncing of the puck off of the paddle. You could also try to add in the possibility of the puck bouncing off the bottom part of the paddle so the x would stay the same, but the y would change. While this might initially sound like a great thing to do it doesn't necessarily add a lot to the game so try it out and decide for yourself whether it is worth the extra effort.
One problem that you might come across is a shimmy. This is where the puck collides repeatedly with the paddle and ends up shimmying up or down the paddle before it finally breaks away. This is something that you want to avoid at all cost because it makes your game seem unprofessional and incomplete. A possible solution for this problem is to check the speed of the puck in the x direction. If the puck is coming towards the paddle then it can be checked for a collision. If on the other hand it is going away from the paddle then we can ignore any collisions that we might think we have.
Testing for collisions between the puck and the edge of the board is identical to the test that we performed when testing for collisions between the paddles and the board. The only tricky part that we have to watch out for here is that we must check the x and y values of the puck against the extents of the board. Again to resolve this particular type of collision all we have to do is negate the x or y speed of the puck depending on the edge of the board that we hit.
This is going to be the same process as what we had to carry out for the paddle to board collision test except in this case we want to check the puck against a limited part of the board. As a starting point lets assume that the goal area consists of the entire left and right hand parts of the board. This means that we can check the x value of the puck against 0 and the maximum width of the board. If we register a collision here then we must increase the score for the appropriate player and put the puck back at the center of the board.
The fourth and final phase is to loop. The loop as we discussed before is accomplished by using threads and the run method. If you are unsure about this you can either refer back to the threads section or check out the sample code for the air hockey game which is available on my web site.
You are now well on the way to creating a great air hockey game. Once you have a basic game up and running you should start playing around and trying to make additions and alterations to your game. Once you have finished your game I would appreciate it if you would send me a copy of your code ([email="firstname.lastname@example.org"]email@example.com[/email]) and your applet so I can post it on my web page for others to see and learn from. For now my web site is www.cpsc.ucalgary.ca/~kinga, but I'm in need of a new location so when I find one I will inform you of the new address.
Well we covered a lot of ground in this article. Hopefully you have you learned quite a bit and are starting to get ideas about how you could take what you have learned so far and make a game of your own. In part 3 I am going to cover using the AWT for buttons, checkboxes, etc., some basic points on classes and code organization, and neural networks. Then in part 4 we will go into arrays and how they can be used for games like tetris, nibbles, and anything else that can be put on a grid. If you want to check out some sample java applets you can come to my web page, www.cpsc.ucalgary.ca/~kinga, which will be operational until the end of the summer, but then I will be in search of a new web space because I'm graduating from university. If you have any questions or comments feel free to email me at [email="firstname.lastname@example.org"]email@example.com[/email].