1. Trang chủ
  2. » Công Nghệ Thông Tin

Black Art of Java Game Programming PHẦN 8 pdf

98 351 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Black Art of Java Game Programming
Tác giả Joel Fan Sams
Trường học Macmillan Computer Publishing
Chuyên ngành Computer Science
Thể loại book
Năm xuất bản 1996
Thành phố Unknown
Định dạng
Số trang 98
Dung lượng 6,16 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Black Art of Java Game Programmingby Joel Fan Sams, Macmillan Computer Publishing ISBN: 1571690433 Pub Date: 11/01/96 Previous Table of Contents Next Defining the Sprite Images Next co

Trang 2

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

Defining the Sprite Images

Next comes the tedious job of defining each type of Sprite’s default “look” This is used in the absence of an animation array of Images To speed things up, rather than actually drawing the Sprite every time it is

required, we will have the program generate an offscreen Image and put it in the animation array Subsequent calls can simply make use of the default Image Besides raw speed, the advantage of this is that the same code can be used whether the Sprite is preanimated or not Here’s the method that will take care of creating the default Image:

public synchronized void generateImage() {

if( im==null) return;

Graphics blah = im.getGraphics();

anim = new Image[1];

Trang 3

This takes care of all of our default IDs except MESSAGE, which we will handle later We declare this

method to be synchronized because we don’t want the Sprite to try to draw the Image before it is finished

being created Next, let’s actually create the drawing methods:

public void paintSprite( Graphics g) {

Animating the Sprites

Notice how this method works just fine whether or not we have given this Sprite a nifty animation array However, if there is an animation array, we need a method that will advance it as necessary This method should also move the Sprite to its new location based on the speed attributes:

public void advance() {

if( anim != null) {

Be sure to understand that a negative speedX or speedY value is totally legitimate, and is used to move the

Sprite in the left or up directions, respectively The last method required for a fully functional Sprite class is the one that makes it actually do something If you remember that Sprite extends Thread, you should realize it needs a run() method:

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/659-664.html (2 von 4) [13.03.2002 13:19:34]

Trang 4

public void run() {

Scrolling the Background

The scrolling background is best accomplished in two parts: the terrain (foreground) and the star field

(background) Because the Terrain uses some pretty wacky methods to achieve great speed, we will write it as its own class, and not as a subclass of Sprite However, just to demonstrate how useful the Sprite class is, we will write the StarField class as an extension of it

Understanding the Terrain Class

How does one write a scrolling foreground? Many games use a graphic that is longer than the active playing field and that looks exactly the same at both ends This can then be scrolled at high speeds past the user, and when it “runs out,” another copy of it (or another compatible graphic) is added This works pretty well, but the repetitive nature of it can be quite annoying to the user if the graphic is not large enough Of course, the

drawback to a large graphic is that it requires large amounts of memory to store, and time to develop

To avoid the drawbacks associated with predrawn terrains, we are going to create a “dynamic” terrain that is continuously generated by a Thread specifically written for this task A “terrain” looks something like Figure 16-2

Figure 16-2 Terrain to be scrolled

This may seem complicated to draw However, if you look at the terrain as a series of polygons, each with four sides, a way of creating it should become clear Figure 16-3 gives an example

Figure 16-3 Same terrain divided into quadrilaterals

To create this effect we will call upon the Polygon class The terrain will be composed of a series of Polygons, each the same width, that have at least two points in common This is highlighted in Figure 16-4 Every time the terrain is “scrolled,” the leftmost Polygon is removed and a new one is generated at the far right At high speeds, this gives the illusion that the user is whizzing by a landscape tremendously fast

Figure 16-4 Terrain coordinate system

Trang 5

Coding the Terrain Class

The Terrain class will use a Vector to store the number of Polygons of a specified width In order to avoid using double-buffered graphics (which are just too slow for this) and to also avoid much flicker (which would ruin the high-speed effect), we will be using some tricky Graphics methods

Declaring Variables

To begin with, we start with some simple variable declarations Notice that many of them are similar to ones

we used in the Sprite class

import java.awt.*;

import java.util.*;

import java.lang.*;

import Sprite;

public class Terrain extends Thread {

Vector v; // stores the Polygons

int WIDTH; // pixel width of one Polygon

Rectangle bounds; // Rectangle bounding the entire Terrain

Graphics theG; // Graphics to draw on

int lasty=0; // the y-coordinate of the "last" Polygon created

}

Previous Table of Contents Next

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/659-664.html (4 von 4) [13.03.2002 13:19:34]

Trang 6

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

Initializing

Now we initialize the Terrain To do this, we require that the creating class pass along three things: the width

of each Polygon, the Terrain’s bounding Rectangle, and the Graphics context to be used Then, we calculate the size of the Vector needed to store the Polygons (the number of Polygons of the requested width that would fit within the bounding Rectangle) Then we actually create the first set of Polygons needed to draw the

Creating the nextPoly() Method

Of course, we haven’t written the nextPoly() method, so let’s do that next It creates a Polygon of random height starting with the Y coordinate of the last Polygon created:

Polygon nextPoly() {

Polygon p = new Polygon();

p.addPoint( (int)(WIDTH/2), (int)(bounds.height * Math.random())); p.addPoint( 0, lasty);

p.addPoint( 0, bounds.height);

p.addPoint( WIDTH, bounds.height);

p.addPoint( WIDTH, lasty = (int)(bounds.height * Math.random()));return p;

}

Notice that we create the Polygon using x and y coordinates that are relative to an X coordinate of zero When

we draw the actual Polygon onto the screen, we are going to need to shift it over before it is drawn, and for that we need a new method:

Trang 7

void paintPolyXY(Graphics g, Polygon p, int x, int y) {

Drawing Terrain onto the Screen

Next, let’s write the method that actually draws the entire Terrain onto the screen For this, we must take each Polygon out of the Vector and make a copy of it, because the method we just wrote for painting the Polygon actually changes its X and Y coordinates We then send the copy to be painted, and leave the original alone: public void paintAll( Graphics g, Rectangle r) {

g.setColor(Color.black);

g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height);

g.setColor( Color.yellow);

for(int x=0;x<v.size();x++) {

Polygon p2 = (Polygon) v.elementAt(x);

Polygon p = new Polygon(p2.xpoints,p2.ypoints,p2.npoints);

paintPolyXY(g,p,r.x+x*WIDTH,r.y);

}

}

Using copyArea() to Enhance Performance

Although we could go ahead now and write the Thread code that makes the Terrain go, the result would be horrendously slow Even if we used double-buffered graphics to draw each successive frame of the Terrain, the method is simply not fast enough, especially when there are many Threads running simultaneously (as there will be in the final product) The key to solving this problem is to recognize that 90 percent of the

Terrain (all but one Polygon) doesn’t change each time Therefore, it is wasteful and inefficient (not to

mention slow) to have to redraw the entire Terrain for each frame Rather, we can take advantage of a

Graphics method called copyArea(), which actually copies a rectangular set of bits from one location on a Graphics context to another Using this method, we can easily copy the majority of the Terrain to the left, and then only draw the one remaining Polygon right next to it Here’s the code:

public void paintChange( Graphics g, Rectangle r) {

Trang 8

Now it’s time to finish up the Thread-related methods of Terrain.class First, there is a method that will be called each frame to dispose of the oldest Polygon and to add a new one:

public void advance() {

And last but not least, we create the run() method in order to actually give this class some functionality!

public void run() {

Trang 9

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

Coding the StarField Class

The StarField is quite simple, really All we have to do is keep track of a big array of X and Y coordinates At each coordinate, we draw a tiny little white line, on a black background, to represent a star Because we will make StarField an extension of Sprite, we can use its “speed” to determine not only the amount each star will move, but also the length of each star Thus, when the Sprite “warp” factor is set to a large number, the stars will appear to streak by at amazing warp speeds! Like the Sprite class, this first version of StarField will use simple onscreen graphics, but eventually we will convert it to double-buffered form

We begin with two integer arrays for the X and Y coordinates We initialize the arrays so that they are full of

-1 The method we will write for creating new stars will search the array for a star that is off the screen (that is, one with an X or Y coordinate that is less than zero)

Let’s get started:

StarField( int num, Rectangle r, Image im) {

this.im=im; // we’ll use this later for double-buffered graphics DELAY = 300;

Trang 10

The initialization method makes a call to a method that adds a star to the array at position i, so let’s code that:

public void addStar(int min) {

int i,j;

for(i=0;i<NUM_STARS;i++)

if(x[i]==-1 && y[i]==-1) {

x[i] = bounds.x+min+(int)((bounds.width-min)*Math.[return]random());

y[i] = bounds.y+(int)(bounds.height*Math.random());

}

}

This picks a random Y position and a random X position (near the right edge) and adds the “star” to the

coordinate arrays Next let’s write a little method to “kill” a star that is no longer needed (i.e., one that has scrolled off the screen):

public void killStar(int i) {

x[i]=-1;

y[i]=-1;

}

The default advance method for Sprite will obviously not work for StarField, so let’s code that next:

public void advance() {

(remember that getSpeed() takes the “warp” factor into account):

public void paintSprite(Graphics g) {

Trang 11

Checking Out the Environment

If you want to see what all that we have accomplished looks like put together, you should take the time now to code up a very simple applet that uses the classes as we’ve developed them so far A simple applet that creates

a StarField, Terrain, and Sprite is just fine Although we won’t walk through the code for such a game right now (WordQuest should demonstrate how it all works), you can find the source code to a game called

SpaceDeath on the CD-ROM that accompanies this book That game shares many attributes with WordQuest (including some things we haven’t yet talked about), but isn’t nearly as refined I would recommend it only as

an example of how these classes can be used to create other games If you want to really see them in action, move right along to the next section…

On with the Quest

It’s time to start talking about our primary goal for this chapter: WordQuest There are many refinements that need to be made to the classes we just developed in order to get them ready for WordQuest In addition, we need to create some new classes and the very lengthy WordQuest class itself WordQuest is so long mainly because it has the responsibility of coordinating all of the various elements involved in the game: It must play referee, scorekeeper, and line judge To accomplish all of this, we should start by creating some new classes to help with the task

Previous Table of Contents Next

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/667-669.html (3 von 3) [13.03.2002 13:19:35]

Trang 12

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

Creating the Question Class

WordQuest works by presenting a question to the user and then presenting several possible answers In order to manage this data, we should create a data structure for storing a question and its possible answers We will assume that all questions are meant to have five possible answers, but it is quite easy

to change this number for games you may create in the future Another feature of the Question class is

a static Vector that we will use to store all of the answers to all of the questions This will enable the Question class to generate random incorrect answers in case there is a question with less than five options specified

Since Question is a small class used only by WordQuest, there is no reason for it to be a public class Therefore, we add it to the same file that the WordQuest class itself will be added to, WordQuest.java

We begin with a static Vector used to store the big list of all answers This is used by every instance of Question that is created We also have regular instance variables to store the text of the question, the correct answer, and another Vector to store the list of possible answers for this question:

class Question {

static Vector biglist = new Vector();

public String question,correct;

Vector answers;

}

Every Question must be instantiated with the text of the question and the correct answer This answer

is also added to the big list via the addAnswer() method:

Trang 13

if( !biglist.contains(a))

biglist.addElement(a);

}

Notice that we carefully avoid duplicating any answers in the big list

Next, we add a few more methods for accessing the Question The first simply provides the text of the Question to be asked The second extracts (and removes) an answer from the Question’s list of

possible answers If this list is empty, it returns null:

public String getQuestion() {

int rand = (int)(Math.random()*answers.size());

String temp = (String)answers.elementAt(rand);

answers.removeElementAt(rand);

return temp;

}

The last method required is one that returns a random answer from the big list This will usually be

used when takeAnswer() returns null:

public static String randAnswer() {

int rand = (int)(Math.random()*biglist.size());

return (String)biglist.elementAt(rand);

}

That’s all there is to the Question class It doesn’t seem like much, but it makes handling the question and answer data superlatively easier

Getting the Question Data

The Question data must be stored in a data file on the same server as the applet Rather than make the applet do the work of reading and parsing that data, we can create another small helper class for

WordQuest, a Thread that reads data into a Vector of Questions This class is instantiated with a URL that points to a data file, as well as a Vector in which the data should be stored:

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/669-673.html (2 von 4) [13.03.2002 13:19:36]

Trang 14

class dataThread extends Thread {

Q: This is a question

A: This is the correct answer

A: This is an incorrect answer

A: This is also an incorrect answer

Q: This is the next question

A: This is the next question’s correct answer

etc

Note that the correct answer is assumed to be the one immediately following the Question, and that the file may specify as many as four incorrect answers (five answers total) If there are fewer than five total answers, wrong answers will be drawn from another question set Here is how the Thread does its thing:

public void run() {

Trang 15

/* use this for debugging

if( temp != null) {

Trang 16

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

Writing the Prompt Frame

Just like NetOthello in Chapter 15, WordQuest will use a Frame to get input from the user at the start of the game However, this promptFrame will also track and display high scores using the HighScoreManager created in Chapter 8, Implementing a High Score Server on a Network We aren’t doing anything new in this code, so it should look familiar If not, you should refer back to Chapters 8 and 15 This code is only used to help out the main WordQuest class, so add it to the same file:

class promptFrame extends Frame {

TextField TF;HighScoreManager HS = new HighScoreManager(10);Panel p;⇒publicString gotName;boolean ready

promptFrame() { super("Ready for a new game?"); setLayout( new⇒ BorderLayout());p = new Panel(); p.setLayout( new FlowLayout());⇒p.add( "West",new Label("Input your name: ")); p.add("West",TF = new⇒TextField(20)); p.add("Center",new Button("OK")); add⇒

("South",p); gotName = null; ready=false;}

public void paint(Graphics g) {

Graphics offG;Dimension d = size();d.height-⇒

=p.preferredSize().height;Image im=createImage(d.width,d.height);

Rectangle r = new

Rectangle(0,0,d.width,d.height);offG=im.getGraphics();HS.paintScores⇒(offG,r);g.drawImage(im,0,0,null);}

public boolean handleEvent(Event evt) {

if( (evt.target instanceof TextField && evt.id==Event.ACTION_EVENT) ||⇒(evt.target instanceof Button && evt.arg.equals("OK"))) {

if( TF.getText() != "") gotName = TF.getText();⇒

ready = true; return true; }return super.handleEvent(evt);}

}

Using ThreadGroups to Synchronize Enemy Sprites

We want the enemy Sprites to come at the user in unison; that is, we want them to be synchronized One

excellent way of doing this is to make sure that they are all in the same ThreadGroup This also makes them more manageable, and allows us to call certain methods (like suspend() and stop()) on the entire group at once Furthermore, if we make the group containing the enemy Sprites part of a larger group that

encompasses all Sprites, we can call these methods in all of the Sprites in the game at once The hierarchy looks something like Figure 16-5

Trang 17

Figure 16-5 ThreadGroup hierarchy in WordQuest

In order to implement these ThreadGroups, we must declare each Sprite to be part of a ThreadGroup at the time it is instantiated Once a Thread is in a group, it is there for good This requires a few changes to the initialization method of Sprite:

Sprite( ThreadGroup p, String d) {

super(p,d);

}

When you create a Thread within a ThreadGroup, you must assign it a name The name is not terribly

important, unless you want to do some Thread operations on only specific Threads to which you do not have a pointer Even though we have no need to do this, we still must provide a name, because that is one of Java’s quirky rules

Extending the Sprite Class

There are a couple of changes to the Sprite class we should make next The first thing we need to do is to add the capacity for each Sprite to have a certain data string associated with it This will be used mainly for the enemy Sprites, each of which must display a potential answer to the question WordQuest poses This mainly affects two methods, setXY() and generateImage() in Sprite.class They need to be altered as follows:

public String data;

if( bounds == null)

bounds = new Rectangle( x,y,WIDTH,HEIGHT);

Trang 18

Because most Sprites will be instantiated with their data as their name, we can change the initialization slightly:

Sprite( ThreadGroup p, String d) {

Trang 19

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

The statusBar Class

Even though we used ID constants to represent different kinds of Sprites, there are still some instances when extending the Sprite class is appropriate One such instance is for the status bar that appears above the playing area This is merely a Sprite that stays still, and whose data is comprised of several numeric variables Instead of moving every time it is advanced, it recalculates the value of its data string The status bar uses the MESSAGE ID in the Sprite class, so first we should add a case for MESSAGE to the generateImage() method:

public synchronized void generateImage() {

Now we can create the statusBar class itself There are four properties we need to track:

• int score This represents the user’s score

• int level This is the current “level” the user has attained

• int lives This is the number of “lives” the user has left (number of times the user can die)

• String quest This is the text of the current question to be displayed

In order to create the data string to be displayed, we must concatenate all of these values like this:

data="Score: "+score+" Lives: "+lives+" Level: "+level+" "+quest;

This is what happens every time the status bar is advanced However, it is inefficient to do these

calculations if nothing has changed since the last time they were performed, so we use a boolean

variable to keep track of whether any change has occurred to any of the values that the status bar tracks Here’s the first bit of code:

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/676-679.html (1 von 5) [13.03.2002 13:19:37]

Trang 20

You see here how the update variable helps save time However, this means that whenever a data

variable is accessed, the update variable must get switched to true Thus, no data variable can be

accessed directly Here are the wrapper functions that we will need:

public void addScore(int amt) {

Trang 21

That’s all there is to this class All other methods are taken care of by Sprite

The userSprite Class

There are several properties that we could decide to give the user’s object, but for now we just want to

be sure that the user doesn’t try to leave the clipping Rectangle of the current Graphics context (which means we’d better be sure to clip it to the playing field):

Trang 23

boolean playing = false; // start with high scores

Previous Table of Contents Next

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/676-679.html (5 von 5) [13.03.2002 13:19:37]

Trang 24

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

The init() Method

Getting everything initialized is a pretty tough task, but let’s get it done Start by dividing the screen into three distinct regions for the status bar, the StarField (playing area), and the Terrain, respectively:

public void init() {

d = size();

statRect = new Rectangle(0, 0, d.width, 16);

SFrect = new Rectangle( 0,statRect.height, ⇒

Define our ThreadGroups:

everything = new ThreadGroup( "all");

group = new ThreadGroup( everything,"enemies");

bullets = new ThreadGroup(everything,"bullets");

Create the status bar and prompt window:

bar = new statusBar(everything, "statusbar");

Trang 25

And last, but not least, get the “kicker” started:

kicker = new Thread(this);

kicker.setPriority(Thread.MAX_PRIORITY);

kicker.start();

}

Using Double-Buffered Graphics

Because we want to do the run() method next, we need to convert all of our Sprite classes to use

double-buffered graphics This isn’t too hard, especially since we decided to have each Sprite create its own Image before it actually does any drawing Here’s the game plan: We have each Sprite work

on creating its Image and then tracking its own location Each time WordQuest wants to redraw, it simply queries each Sprite in the game, and draws that Sprite’s Image at the proper location Keep in

mind that this means the StarField must be drawn first, or some Sprites may appear to vanish.

Double-Buffering the Sprite Class

The only real change to the Sprite class that is required is that it no longer has to draw itself on the Graphics context This means we can change run() to look like this:

public void run() {

In addition, we want to add another method to make it easier to query the current Image:

public Image currentImage() {

if( anim== null)

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/679-683.html (2 von 5) [13.03.2002 13:19:38]

Trang 26

public void run() {

StarField uses a boolean flag that is only false after it is done creating its Image This is necessary

because Java allows other classes to access the Image while it is still being generated, and this allows incomplete versions of it to be drawn onto the screen—a very tacky effect.

See WordQuest Run

The WordQuest run() method does the following: First, it checks to see if we are playing If so, it runs the check method to handle any collisions, and so on Then, it creates a new offscreen Image, and prepares it for drawing It draws the StarField, and then all other Sprites on the Image, and then it draws the Image onto the current Graphics context

If the user is not playing, it checks to see if the promptFrame is “ready” (i.e., if the user has pressed the OK button) If so, it starts the game up again by calling all the game-starting methods If the user

is not ready to start playing, it just goes to sleep for a bit and checks again later.

That’s how the run() method works, and we are going to code it before we code any of the methods to which it refers This is so you can get a better handle on how the various methods interact as we write them.

Here’s the run() code:

public void run() {

/* don’t draw the StarField until it is ready (flag == false) */

try{ while(SF.flag) kicker.sleep(100); } catch(Exception e);

Trang 27

To accompany the run() code we’ll need those other crucial Thread methods, start() and stop():

public void start() {

questions = new Vector();

try{

URL theURL = new URL(getCodeBase(), "data.txt");

new dataThread( questions, theURL).start();

} catch (Exception e);

Trang 28

Previous Table of Contents Next

Trang 29

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

Handling the Rules

Arguably the most crucial method in WordQuest is the method that does all of the game-related checking This implements the “rules” of WordQuest This method decides the fate of every Sprite and imposes the rules of the game on everyone First, it turns all active enemies and bullets into arrays of Sprite:

public void doCheck() {

Undoubtedly you would want to replace “You lose” with a friendlier message (maybe even a graphic or

pop-up window); this is left as an exercise Next, we do all of the Sprite checking Whenever a collision occurs,

we call the collision() method in the affected Sprites In order to save processing time, we only check those

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/683-686.html (1 von 4) [13.03.2002 13:19:38]

Trang 30

Sprites that have an effect on each other (i.e., user vs Enemy) However, if we wanted to, we could just check every Thread in the “everything” group against every other Thread, and call the appropriate collision

methods Also, notice that we suspend a Sprite while we are checking it, in order to ensure that it does not move while it is being checked (that is cheating!) Another nifty feature is that when a bullet hits an incorrect answer, it is deflected back at the user This adds a bit more action to the game, although some players might find it a bit too difficult to handle If you find this to be the case, the feature can easily be disabled

if( user.intersects( sprites[x].bounds)) {

user.collision( user.id * sprites[x].id);

sprites[x].collision( user.id * sprites[x].id);

}

for(int y=0; y < bullets.activeCount(); y++) {

Sprite bullet = bul[y];

if( sprites[x].intersects( bullet.bounds))

if( currentQ.correct.equals( sprites[x].data)) {

Trang 31

We start by stopping any currently active enemy Sprites Then, we check to see if there are any questions available from which to choose If not, we spawn a new dataThread to read some in from the data file:

public void nextQuestion() {

group.stop();

if( questions.isEmpty())

try{

URL theURL = new URL(getCodeBase(), "data.txt");

new dataThread( questions, theURL).start();

} catch (Exception e);

Next, we wait until there is at least one Question in the Vector that the dataThread is supposed to fill:

} while( currentQ == null );

Once that is complete, we assign the new question to the status bar, and then attempt to find a list of five possible answers to the question We want to be especially sure that the correct answer appears once and only once in every set of five:

bar.setQuestion( currentQ.question);

String dataArray[],temp;

dataArray = new String[ 5 ];

boolean flag = false;

Trang 32

Once we have the text of the answers, we can create the enemy Sprites themselves Once we have them started, we suspend them immediately Once they are all created and in the appropriate ThreadGroup, we resume them all at one time This helps ensure that they are closely synchronized

Trang 33

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

Initializing Once More

One method that was called from doCheck() was designed to create the user’s object and initialize it

properly This also creates a new StarField and/or Terrain if one has not been instantiated already (although this could have been accomplished elsewhere)

This method is pretty straightforward, and here’s what it looks like:

if( user != null && user.isAlive()) user.stop();

user = new userSprite(everything, "user");

Creating a Firing Mechanism

Whenever the user wants to “shoot” an answer, we need to create a bullet and send it hurtling toward its destination To add a little intrigue to the scoring mechanism, we also dock the user one point for each bullet

he or she fires Here’s the code for a method that creates a new bullet at a given X and Y coordinate:

public Sprite newBullet(int x, int y) {

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/686-689.html (1 von 4) [13.03.2002 13:19:38]

Trang 34

public boolean mouseDown(Event evt, int x, int y) {

Sprite b= newBullet(user.x,user.y);

b.start();

return true;

}

Giving the User Control

We added one control for debugging purposes above, but let’s also add the code that allows the player to move his or her ship around First, we need to handle movement This is accomplished with the numeric keypad We use the numbers 1–9 to represent eight different directions that the user could travel in (N, W, S,

Trang 35

Last, we add one of WordQuest’s neatest special effects: the warp factor When the user holds down the

<W> key, his or her ship kicks into warp mode, and everything flies past much faster The StarField also makes a cool streaking effect To do this, we must first have a keyDown event that turns on the warp effect:

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/686-689.html (3 von 4) [13.03.2002 13:19:38]

Trang 36

case 'w': {

Sprite.warp= 5;

return true;

}

This, however, requires a corresponding keyUp event that turns off the effect:

public boolean keyUp(Event evt, int key) {

Although WordQuest is practically finished, there is still one small matter that we have not resolved We

discussed earlier the idea of Thread synchronization This is very important in Java, and is something worth

discussing further If you have played WordQuest at all, you will notice that although the enemy Sprites, being in the same group, run pretty much at the same rate, they are not totally synchronized Some Sprites run faster, some slower, depending on how resources get allocated in each cycle This effect is not visually appealing, so we need to come up with a way of having absolute synchronization among Sprites

The key to synchronization is having each Sprite keep in touch with all of the other Sprites it needs to

synchronize with While there are many ways to accomplish this, we will explore one simple way Once you understand the basic concept, it is trivial to extend it to more complicated examples

Previous Table of Contents Next

Trang 37

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

The syncSprite Class

To achieve Thread synchronization among enemy Sprites, we are going to create a new subclass of Sprite, called syncSprite Each syncSprite will not advance itself until all of the other syncSprites in its

ThreadGroup have advanced To do this, we use a counter variable When each syncSprite finishes

advancing, it increments this counter in every syncSprite in the Group When the value of this counter

reaches the number of syncSprites in the Group, it is reset and the process repeats The syncSprite will not advance until this condition is met The net result of all this inter-Thread communication is that no

syncSprite can get more than one frame ahead or behind its siblings The main drawback is that all

syncSprites move as slowly as the one with the least amount of resources allocated to it However, if all the syncSprites have the same priority level, this should not be much of a problem

The code for this class is not very complicated once you understand the concepts involved We use a simple integer as a counter, and we use a boolean flag to ensure that each Sprite gets run for the first frame (without this, if one Sprite advanced ahead of the others, their counters would be set to 1, and they would never

advance) We also take advantage of the yield() method to give up the Sprite’s time slice to another waiting Thread (most likely another syncSprite in the same Group)

Trang 38

s = new syncSprite( group, dataArray[x]);

That’s All Folks!

Congratulations! WordQuest is totally functional now Go ahead and compile it Of course, you will need some questions to be in a data file called data.txt, in the same directory as your compiled class files If you want, you can borrow some that I created that are on the CD-ROM that accompanies this book They are mainly taken from preparatory material for the Scholastic Aptitude Test (SAT-1) Note that although

WordQuest was designed for vocabulary training, it could easily be used for math or other subjects

Beautiful Friend, the End

WordQuest is now ready to go! Compile it, play it, show it off to your friends Who knows, you might even learn some new words!

Suggestion Box

There are many, many things you could do to make WordQuest even more fun This list includes a few:

• Like any other game, WordQuest would benefit from some dazzling graphics or sound effects

Inserting these into the game is very easy, since we have already established methods and structures for handling arrays of Images designed to animate Sprites Sounds would require a few lines of new code, but nothing tricky

• Now that our enemy Sprites are synchronized, you could have them fly in formation This would

require having a method that was aware of different patterns that the Sprites could be staggered in, and that set them up in cool patterns Because the Sprites are synchronized, they will maintain this pattern almost exactly

Trang 39

• Implement different levels Although we have a variable in the status bar for “level,” we never

actually use it in WordQuest You could have each level use a different set of questions, so that the game gets progressively more difficult Or, perhaps the Sprites could move faster as levels increase You make the call

• Write the EXPLOSION Sprite! This is simply an array of Images that animate an explosion Write

a special explosionSprite class that displays itself and then stops; then write up a method in either Sprite or WordQuest itself that calls for an explosion to replace dead Sprites

Summary

In this chapter, we created a nifty educational game while getting some practical experience with some Java concepts, like Thread synchronization In addition, we combined many of the techniques learned throughout the book into an integrated gaming adventure WordQuest is a formidable game in its own right, and it lays the foundation for many enjoyable action games to come Education and action are both game genres with a considerable audience, and it is important for any game programmer to understand the underlying concepts that make both possible

Previous Table of Contents Next

file:///D|/Downloads/Books/Computer/Java/Blac 20Java%20Game%20Programming/ch16/690-692.html (3 von 3) [13.03.2002 13:19:39]

Trang 40

Black Art of Java Game Programming

by Joel Fan

Sams, Macmillan Computer Publishing

ISBN: 1571690433 Pub Date: 11/01/96

Previous Table of Contents Next

simple, it uses Java features such as multiple classes, threaded classes, and GUI components, and is therefore a good example of effectively using the power of Java to create an elegant program

Playing Magic Squares

Playing this game is similar to most puzzle games, in that it is simple and easy to learn Figure 17-1 shows the layout of the applet on the HTML page The grid of squares at the top is what the user clicks on to play the game Clicking on this grid causes the squares to begin a color-cycling routine

Figure 17-1 The Magic Squares applet running

Ngày đăng: 12/08/2014, 09:21

TỪ KHÓA LIÊN QUAN