chapters 3 and 4: game display modes - intro these two...
TRANSCRIPT
Chapters 3 and 4: Game Display Modes - Intro
• These two chapters implement a simple game
• The purpose is to demonstrate various display modes:
1. Window-based
2. Applet
3. Full screen
• Once again, understanding the graphics aspects is not important - they aredealt with in subsequent chapters
• The game:
– The game is called Worms
– A worm (similar to the creature in Centipede) moves rapidly around thescreen
– Your task is to click on its head
– Your score is based on the number of misses and the time it takes to com-plete the task
– When you miss, an obstacle is placed at the point you clicked
– The worm must go around the obstacles
– Wraparound is in effect
– As the game progresses, the worm grows in length to a fixed maximum
• The main class is WormPanel
– It is the game’s version of GamePanel discussed earlier, and is essentiallythe same
• The primary difference between the different versions of the game resides inthe top-level class
– The window-based game inherits from JFrame, the applet-based one fromJApplet
– This difference affects various aspects of game control
• The game incorporates code for generating statistics (like those gathered inChapter 2)
1
Chapters 3 and 4: Game Display Modes - Windowed Version, WormChase Class
• The abbreviated class diagram:
• There are two threads:
– WormChase is the top-level class and manages window events and the GUI
– WormPanel maintains the animation loop
2
Chapters 3 and 4: Game Display Modes - Windowed Version, WormChaseClass (2)
• WormChase class
– Class diagram:
– main() reads in the frame rate from the command line
∗ This is converted to a frame quantum in nanoseconds
∗ There is a default FPS if no argument is provided∗ Code:
public static void main(String args[])
{
int fps = DEFAULT_FPS;
if (args.length != 0)
fps = Integer.parseInt(args[0]);
long period = (long) 1000.0/fps;
System.out.println("fps: " + fps + "; period: " + period + " ms");
new WormChase(period*1000000L); // ms --> nanosecs
}
3
Chapters 3 and 4: Game Display Modes - Windowed Version,WormChase Class (3)
– No buttons are provided for game control
∗ Control is handled by WindowListener methods
∗ The game is paused when the window loses focus, and resumed when itregains focus
∗ The game terminates when the window is killed∗ Code:
public void windowActivated(WindowEvent e)
{ wp.resumeGame(); }
public void windowDeactivated(WindowEvent e)
{ wp.pauseGame(); }
public void windowDeiconified(WindowEvent e)
{ wp.resumeGame(); }
public void windowIconified(WindowEvent e)
{ wp.pauseGame(); }
public void windowClosing(WindowEvent e)
{ wp.stopGame(); }
public void windowClosed(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public void resumeGame()
// called when the JFrame is activated / deiconified
{ isPaused = false; }
public void pauseGame()
// called when the JFrame is deactivated / iconified
{ isPaused = true; }
public void stopGame()
// called when the JFrame is closing
{ running = false; }
4
Chapters 3 and 4: Game Display Modes - Windowed Version, WormPanel ClassArchitecture
• Class diagram:
5
Chapters 3 and 4: Game Display Modes - Windowed Version, WormPanel ClassConstructor
• The constructor:
public WormPanel(WormChase wc, long period)
{
wcTop = wc;
this.period = period;
setBackground(Color.white);
setPreferredSize( new Dimension(PWIDTH, PHEIGHT));
setFocusable(true);
requestFocus(); // the JPanel now has focus, so receives key events
readyForTermination();
// create game components
obs = new Obstacles(wcTop);
fred = new Worm(PWIDTH, PHEIGHT, obs);
addMouseListener( new MouseAdapter() {
public void mousePressed(MouseEvent e)
{ testPress(e.getX(), e.getY()); }
});
// set up message font
font = new Font("SansSerif", Font.BOLD, 24);
metrics = this.getFontMetrics(font);
// initialise timing elements
fpsStore = new double[NUM_FPS];
upsStore = new double[NUM_FPS];
for (int i=0; i < NUM_FPS; i++) {
fpsStore[i] = 0.0;
upsStore[i] = 0.0;
}
} // end of WormPanel()
6
Chapters 3 and 4: Game Display Modes - Windowed Version, WormPanel ClasstestPress()
• testPress()
– Handles hits:private void testPress(int x, int y)
// is (x,y) near the head or should an obstacle be added?
{
if (!isPaused && !gameOver) {
if (fred.nearHead(x,y)) { // was mouse press near the head?
gameOver = true;
score = (40 - timeSpentInGame) + (40 - obs.getNumObstacles());
// hack together a score
}
else { // add an obstacle if possible
if (!fred.touchedAt(x,y)) // was the worm’s body untouched?
obs.add(x,y);
}
}
} // end of testPress()
7
Chapters 3 and 4: Game Display Modes - Windowed Version, WormPanel Classrun()
• Same as before, but with code for printing statistics added (see pp 54 - 56)
public void run()
{
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
gameStartTime = J3DTimer.getValue();
prevStatsTime = gameStartTime;
beforeTime = gameStartTime;
running = true;
while(running) {
gameUpdate();
gameRender();
paintScreen();
afterTime = J3DTimer.getValue();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) { // some time left in this cycle
try {
Thread.sleep(sleepTime/1000000L); // nano -> ms
}
catch(InterruptedException ex){}
overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
}
else { // sleepTime <= 0; the frame took longer than the period
excess -= sleepTime; // store excess time value
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield(); // give another thread a chance to run
noDelays = 0;
}
}
beforeTime = J3DTimer.getValue();
int skips = 0;
while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
excess -= period;
gameUpdate(); // update state but don’t render
skips++;
}
framesSkipped += skips;
storeStats();
}
printStats();
System.exit(0); // so window disappears
} // end of run()
8
Chapters 3 and 4: Game Display Modes - Windowed Version, WormPanel ClassgameUpdate(), gameRender(), and printScreen()
• gameUpdate()
– Calls fred.move(), which handles the updates to the worm (fred)private void gameUpdate()
{ if (!isPaused && !gameOver)
fred.move();
} // end of gameUpdate()
• gameRender()
– Responsible for drawing - worm and obstacles
– Also handles statistics output
– Otherwise, like gameRender() from GamePanel
private void gameRender()
{
if (dbImage == null){
dbImage = createImage(PWIDTH, PHEIGHT);
if (dbImage == null) {
System.out.println("dbImage is null");
return;
}
else
dbg = dbImage.getGraphics();
}
// clear the background
dbg.setColor(Color.white);
dbg.fillRect (0, 0, PWIDTH, PHEIGHT);
dbg.setColor(Color.blue);
dbg.setFont(font);
// report frame count & average FPS and UPS at top left
// dbg.drawString("Frame Count " + frameCount, 10, 25);
dbg.drawString("Average FPS/UPS: " + df.format(averageFPS) + ", " +
df.format(averageUPS), 20, 25); // was (10,55)
dbg.setColor(Color.black);
// draw game elements: the obstacles and the worm
obs.draw(dbg);
fred.draw(dbg);
if (gameOver)
gameOverMessage(dbg);
} // end of gameRender()
• printScreen()
– No changes
9
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm ClassStructure
• The worm’s structure defined by
private Point cells[];
private int nPoints;
private int tailPosn, headPosn; // the tail and head of the buffer
public Worm(int pW, int pH, Obstacles os)
{
cells = new Point[MAXPOINTS]; // initialise buffer
nPoints = 0;
headPosn = -1; tailPosn = -1;
• The body is represented by cells
– Each cell stores a point that represents a body section (or the head)
– To move the worm, the tail is erased and a new head is added to the nextavailable cell
– It is a circular array
10
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm ClassBearing
• Bearing (heading)
– The worm’s bearing/heading is represented by an integerprivate static final int NUM_DIRS = 8;
private static final int N = 0; // north, etc going clockwise
private static final int NE = 1;
private static final int E = 2;
private static final int SE = 3;
private static final int S = 4;
private static final int SW = 5;
private static final int W = 6;
private static final int NW = 7;
private int currCompass; // stores the current compass dir/bearing
– Graphically:
– currCompass holds the current heading/direction/bearing
11
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm ClassBearing (2)
• Changing bearing
– The new direction is determined by varyBearing()
∗ The greatest probability is to go straight (33.3%)
∗ Of lesser probability is to veer 45 degrees (22.2 % L/R)
∗ Of least probability is to veer 90 degrees (11.1 % L/R)
∗ The change of direction is represented as an int stored in probsForOffsetprobsForOffset = new int[NUM_PROBS];
probsForOffset[0] = 0; probsForOffset[1] = 0;
probsForOffset[2] = 0; probsForOffset[3] = 1;
probsForOffset[4] = 1; probsForOffset[5] = 2;
probsForOffset[6] = -1; probsForOffset[7] = -1;
probsForOffset[8] = -2;
private int varyBearing()
// vary the compass bearing semi-randomly
{
int newOffset = probsForOffset[ (int)( Math.random()*NUM_PROBS )];
return calcBearing( newOffset );
} // end of varyBearing()
– The new bearing is computed in calcBearing(), which adds the offset to thecurrent headingprivate int calcBearing(int offset)
// Use the offset to calculate a new compass bearing based
// on the current compass direction.
{
int turn = currCompass + offset;
// ensure that turn is between N to NW (0 to 7)
if (turn >= NUM_DIRS)
turn = turn - NUM_DIRS;
else if (turn < 0)
turn = NUM_DIRS + turn;
return turn;
} // end of calcBearing()
– For example:
bearing direction offset new bearing new direction
0 N 0 0 N0 N -1 7 NW0 N 1 1 NE2 E -1 1 NE2 E -2 0 N2 E 2 4 S
12
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm ClassMovement
• Calculating the head coords
– The head will be moved one body segment on each iteration
– The new coordinates are based on the size of the circle representing a bodysegment and the computed head offset
∗ A circle is defined by the
1. Coords of its upper left corner
2. Its width and height
∗ The differentials by which to adjust the (x, y) coords are stored in theincr array:
incrs = new Point2D.Double[NUM_DIRS];
incrs[N] = new Point2D.Double(0.0, -1.0);
incrs[NE] = new Point2D.Double(0.7, -0.7);
incrs[E] = new Point2D.Double(1.0, 0.0);
incrs[SE] = new Point2D.Double(0.7, 0.7);
incrs[S] = new Point2D.Double(0.0, 1.0);
incrs[SW] = new Point2D.Double(-0.7, 0.7);
incrs[W] = new Point2D.Double(-1.0, 0.0);
incrs[NW] = new Point2D.Double(-0.7, -0.7);
13
Chapters 3 and 4: Game Display Modes - Windowed Version, WormClass Movement (2)
– The new coordinates are computed by nextPoint():private Point nextPoint(int prevPosn, int bearing)
{
// get the increments for the compass bearing
Point2D.Double incr = incrs[bearing];
int newX = cells[prevPosn].x + (int)(DOTSIZE * incr.x);
int newY = cells[prevPosn].y + (int)(DOTSIZE * incr.y);
// modify newX/newY if < 0, or > pWidth/pHeight: use wraparound
if (newX+DOTSIZE < 0) // is circle off the left edge of the canvas?
newX = newX + pWidth;
else if (newX > pWidth) // is circle off the right edge of the canvas?
newX = newX - pWidth;
if (newY+DOTSIZE < 0) // is circle off the top of the canvas?
newY = newY + pHeight;
else if (newY > pHeight) // is circle off the bottom of the canvas?
newY = newY - pHeight;
return new Point(newX,newY);
} // end of nextPoint()
– When calculating the new coordinates, wraparound at the window edgesmust be considered
• Movement and growth
– The worm starts out as a single cell
– On each iteration, it adds one cell until it is full length
14
Chapters 3 and 4: Game Display Modes - Windowed Version, WormClass Movement (3)
– Movement is achieved by adding a new head
∗ The next available slot in the cells array is used
∗ The array use is circular - when full grown, the new head overwrites thetail
– The number of body segments is maintained by nPoints
– Pointers headPosn and tailPosn keep track of the head and tail positions inthe array
– The combination of growth and movement requires three phases in move():
1. Game start
∗ A head is added to the worm and an initial direction of movementcalculated
2. Worm is not full length
∗ A new head is added to the next available free slot
3. Worm is full length
∗ The new head overwrites the tail
– The head and tail indices are adjusted appropriately in each case
– Example growth:
iteration headPosn tailPosn prevPosn nPoints cells
0 -1 -1 - 0 - - - -1 0 0 -1 1 p1 - - -2 1 0 0 2 p1 p2 - -3 2 0 1 3 p1 p2 p3 -4 3 0 2 4 p1 p2 p3 p45 0 1 3 4 p5 p2 p3 p46 1 2 0 4 p5 p6 p3 p4
– The new head position is generated by newHead(prevPosn)
∗ See the next sections re collisions and obstacles
15
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm ClassMovement (4)
• Worm movement carried out by move():
public void move()
{
int prevPosn = headPosn; // save old head posn while creating new one
headPosn = (headPosn + 1) % MAXPOINTS;
if (nPoints == 0) { // empty array at start of game
tailPosn = headPosn;
currCompass = (int)( Math.random()*NUM_DIRS ); // random dir.
cells[headPosn] = new Point( pWidth/2, pHeight/2 ); // center pt
nPoints++;
}
else if (nPoints == MAXPOINTS) { // array is full
tailPosn = (tailPosn + 1) % MAXPOINTS; // forget last tail
newHead(prevPosn);
}
else { // still room in cells[]
newHead(prevPosn);
nPoints++;
}
} // end of move()
16
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm ClassDrawing
• This is handled by draw() and is straightforward:
public void draw(Graphics g)
// draw a black worm with a red head
{
if (nPoints > 0) {
g.setColor(Color.black);
int i = tailPosn;
while (i != headPosn) {
g.fillOval(cells[i].x, cells[i].y, DOTSIZE, DOTSIZE);
i = (i+1) % MAXPOINTS;
}
g.setColor(Color.red);
g.fillOval( cells[headPosn].x, cells[headPosn].y, DOTSIZE, DOTSIZE);
}
} // end of draw()
17
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm ClassCollisions
• The new head position is generated by newHead()
• If the worm should encounter an obstacle (see below), its new direction ishard-coded in fixedOffs[]
1. If nothing is to the immediate left or right, the worm makes a 90 degreeturn
2. Otherwise, it reverses direction
• newHead():
private void newHead(int prevPosn)
{
Point newPt;
int newBearing;
int fixedOffs[] = {-2, 2, -4}; // offsets to avoid an obstacle
newBearing = varyBearing();
newPt = nextPoint(prevPosn, newBearing );
// Get a new position based on a semi-random
// variation of the current position.
if (obs.hits(newPt, DOTSIZE)) {
for (int i=0; i < fixedOffs.length; i++) {
newBearing = calcBearing(fixedOffs[i]);
newPt = nextPoint(prevPosn, newBearing);
if (!obs.hits(newPt, DOTSIZE))
break; // one of the fixed offsets will work
}
}
cells[headPosn] = newPt; // new head position
currCompass = newBearing; // new compass direction
} // end of newHead()
18
Chapters 3 and 4: Game Display Modes - Windowed Version, Worm Class Hits
• Checking whther the user has clicked on the worm is performed by nearHead()and touchedAt()
1. nearHead() determines if the head was hit
– A hit is considered to be anywhere within one diameter from the head’scenter
– This will terminate the game
public boolean nearHead(int x, int y)
// is (x,y) near the worm’s head?
{ if (nPoints > 0) {
if( (Math.abs( cells[headPosn].x + RADIUS - x) <= DOTSIZE) &&
(Math.abs( cells[headPosn].y + RADIUS - y) <= DOTSIZE) )
return true;
}
return false;
} // end of nearHead()
2. touchedAt() determines if the body was hit
– Note that no obstacles are added on a body hit
public boolean touchedAt(int x, int y)
// is (x,y) near any part of the worm’s body?
{
int i = tailPosn;
while (i != headPosn) {
if( (Math.abs( cells[i].x + RADIUS - x) <= RADIUS) &&
(Math.abs( cells[i].y + RADIUS - y) <= RADIUS) )
return true;
i = (i+1) % MAXPOINTS;
}
return false;
} // end of touchedAt()
19
Chapters 3 and 4: Game Display Modes - Windowed Version, Obstacle Class
• Obstacles are represented as an ArrayList of boxes
• hits() is called by Worm to determine whether the worm has hit an obstacle
• add() adds a new obstacle
• draw() displays the obstacles
• Note that these methods are synchronized as a box could be in the process ofbeing added while the animation loop is accessing the list for drawing
• Code:
public class Obstacles
{
private static final int BOX_LENGTH = 12;
private ArrayList boxes; // arraylist of Rectangle objects
private WormChase wcTop;
public Obstacles(WormChase wc)
{ boxes = new ArrayList();
wcTop = wc;
}
synchronized public void add(int x, int y)
{ boxes.add( new Rectangle(x,y, BOX_LENGTH, BOX_LENGTH));
wcTop.setBoxNumber( boxes.size() ); // report new number of boxes
}
synchronized public boolean hits(Point p, int size)
{
Rectangle r = new Rectangle(p.x, p.y, size, size);
Rectangle box;
for(int i=0; i < boxes.size(); i++) {
box = (Rectangle) boxes.get(i);
if (box.intersects(r)) return true;
}
return false;
} // end of intersects()
synchronized public void draw(Graphics g)
{
Rectangle box;
g.setColor(Color.blue);
for(int i=0; i < boxes.size(); i++) {
box = (Rectangle) boxes.get(i);
g.fillRect( box.x, box.y, box.width, box.height);
}
} // end of draw()
synchronized public int getNumObstacles()
{ return boxes.size(); }
} // end of Obstacles class
20
Chapters 3 and 4: Game Display Modes - Applet Version
• The abbreviated class diagram:
21
Chapters 3 and 4: Game Display Modes - Applet Version (2)
• There is very little difference between this version and the windowed version
1. WormChaseApplet extends JApplet instead of JFrame2. Applet methods are used for game control instead of WindowListener events
public class WormChaseApplet extends JApplet
{
private WormPanel wp; // where the worm is drawn
// ...
public void init()
{
String str = getParameter("fps");
int fps = (str != null) ? Integer.parseInt(str) : DEFAULT_FPS;
long period = (long) 1000.0/fps;
System.out.println("fps: " + fps + "; period: " + period + " ms");
makeGUI(period);
wp.startGame();
} // end of init()
// ...
public void start()
{ wp.resumeGame(); }
public void stop()
{ wp.pauseGame(); }
public void destroy()
{ wp.stopGame(); }
3. startGame() replaces addNotify()
4. There are some changes re printStats()
22
Chapters 3 and 4: Game Display Modes - Full Screen Modes Introduction
• A ”full screen” approach to game display is preferred to the previous methods
– The display window occupies most - if not all - of the display device area
– The programmer generally must provide onscreen buttons for game control,rather than relying on window controls
– It provides a more immersive environment for game play, eliminating thedistraction of other windows, etc.
• Three increasingly fuller screen modes will be presented:
1. Almost full screen
2. Undecorated full screen
3. Full screen exclusive
• Each version adds revisions to the previous, culminating in full screen exclusivemode
23
Chapters 3 and 4: Game Display Modes - Almost Full Screen Mode
• The game window consists of a JFrame object
• OS desktop controls are still visible (e.g., menu bar)
• Game control is via the desktop controls, so no need to provide additional GUIcontrols within the game itself
• The abbreviated class diagram:
– Note that this is the same as the diagram presented at the beginning of thisset of slides
24
Chapters 3 and 4: Game Display Modes - Almost Full Screen Mode,WormChase
• WormChase
– Class diagram:
– In this version, the size of the JPanel will be programmatically computed
∗ The width and height are based on
1. The JFrame’s size
2. The JFrame’s insets
3. The desktop’s insets
4. Size of any other Swing components in the window
∗ An inset is simply a border component
∗ Once these sizes are known, simply subtract them from the screen’s size
∗ Since the JFrame and GUI element dimensions are not available untilthe game window is created, the app is contructed in stages:
1. Construct the JFrame and GUI elements
2. Get their sizes
3. Calculate the resulting size of the JPanel
4. Build the JPanel
25
Chapters 3 and 4: Game Display Modes - Almost Full Screen Mode,WormChase (2)
∗ Since you cannot prevent the user from dragging the game window, theapp is coded to snap the window back into place should the user do so(see addComponentListener( new ComponentAdapter()) in the construc-tor))
∗ Code:import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class WormChase extends JFrame implements WindowListener
{
private static int DEFAULT_FPS = 80;
private WormPanel wp;
private JTextField jtfBox; // displays no.of boxes used
private JTextField jtfTime; // displays time spent in game
private int pWidth, pHeight; // dimensions of the panel
public WormChase(long period)
{
super("The Worm Chase");
makeGUI();
pack(); // first one (the GUI doesn’t include the JPanel yet)
setResizable(false); // sizes may change when resizable
calcSizes();
setResizable(true);
Container c = getContentPane();
wp = new WormPanel(this, period, pWidth, pHeight);
c.add(wp, "Center");
pack(); // second, after JPanel added
addWindowListener( this );
addComponentListener( new ComponentAdapter() {
public void componentMoved(ComponentEvent e)
/* Called by the Component listener when the JFrame is
moved. Put it back in its original position. */
{ setLocation(0,0); }
});
setResizable(false);
setVisible(true);
} // end of WormChase() constructor
26
Chapters 3 and 4: Game Display Modes - Almost Full Screen Mode,WormChase makeGUI and calcSizes
∗ makeGUI() constructs the text output area
∗ calcSizes() computes the size of the JPanel based on insetsprivate void makeGUI()
{
Container c = getContentPane(); // default BorderLayout used
JPanel ctrls = new JPanel(); // a row of textfields
ctrls.setLayout( new BoxLayout(ctrls, BoxLayout.X_AXIS));
jtfBox = new JTextField("Boxes used: 0");
jtfBox.setEditable(false);
ctrls.add(jtfBox);
jtfTime = new JTextField("Time Spent: 0 secs");
jtfTime.setEditable(false);
ctrls.add(jtfTime);
c.add(ctrls, "South");
} // end of makeGUI()
public void setBoxNumber(int no)
{ jtfBox.setText("Boxes used: " + no); }
public void setTimeSpent(int t)
{ jtfTime.setText("Time Spent: " + t + " secs"); }
private void calcSizes()
/* Calculate the size of the drawing panel to fill the screen, but
leaving room for the JFrame’s title bar and insets, the OS’s insets
(e.g. taskbar) and the textfields under the JPanel.
*/
{
GraphicsConfiguration gc = getGraphicsConfiguration();
Rectangle screenRect = gc.getBounds();
Toolkit tk = Toolkit.getDefaultToolkit();
Insets desktopInsets = tk.getScreenInsets(gc);
Insets frameInsets = getInsets(); // only works after a pack() call
Dimension tfDim = jtfBox.getPreferredSize(); // size of text field
pWidth = screenRect.width - (desktopInsets.left + desktopInsets.right)
- (frameInsets.left + frameInsets.right);
pHeight = screenRect.height - (desktopInsets.top + desktopInsets.bottom)
- (frameInsets.top + frameInsets.bottom)
- tfDim.height;
} // end of calcSizes()
27
Chapters 3 and 4: Game Display Modes - Almost Full Screen Mode,WormChase window callbacks and main
public void windowActivated(WindowEvent e)
{ wp.resumeGame(); }
public void windowDeactivated(WindowEvent e)
{ wp.pauseGame(); }
public void windowDeiconified(WindowEvent e)
{ wp.resumeGame(); }
public void windowIconified(WindowEvent e)
{ wp.pauseGame(); }
public void windowClosing(WindowEvent e)
{ wp.stopGame(); }
public void windowClosed(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public static void main(String args[])
{
int fps = DEFAULT_FPS;
if (args.length != 0)
fps = Integer.parseInt(args[0]);
long period = (long) 1000.0/fps;
System.out.println("fps: " + fps + "; period: " + period + " ms");
new WormChase(period*1000000L); // ms --> nanosecs
}
} // end of WormChase class
28
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode
• This version eliminates the window’s title bar
– Window controls cannot be used for game control
– Must provide on-screen buttons and must explicitly check for user interac-tion
• Class diagram:
29
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormChase
• WormChase
– Since not using window to control game, no need for window handlingmethods any longer
– Code:import javax.swing.*;
import java.awt.*;
public class WormChase extends JFrame
{
private static int DEFAULT_FPS = 80;
public WormChase(long period)
{
super("The Worm Chase");
Container c = getContentPane();
c.setLayout( new BorderLayout() );
WormPanel wp = new WormPanel(this, period);
c.add(wp, "Center");
setUndecorated(true); // no borders or title bar
setIgnoreRepaint(true); // turn off all paint events since doing active rendering
pack();
setResizable(false);
setVisible(true);
} // end of WormChase() constructor
public static void main(String args[])
{
int fps = DEFAULT_FPS;
if (args.length != 0)
fps = Integer.parseInt(args[0]);
long period = (long) 1000.0/fps;
System.out.println("fps: " + fps + "; period: " + period + " ms");
new WormChase(period*1000000L); // ms --> nanosecs
}
} // end of WormChase class
30
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormPanel
• WormPanel class
– Constructor creates 2 rectangles to serve as buttons:
1. One for pausing game
2. One for quitting
∗ They exhibit usual behavior of buttons (color and text change, etc.)
∗ See testPress() and testMove() methods and calls they make (triggeredby mouse actions), and drawButtons() (called by gameRender())
31
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormPanel Constructor
– Code:import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.text.DecimalFormat;
import com.sun.j3d.utils.timer.J3DTimer;
public class WormPanel extends JPanel implements Runnable
{
\\Lots of variable declarations
public WormPanel(WormChase wc, long period)
{
wcTop = wc;
this.period = period;
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension scrDim = tk.getScreenSize();
pWidth = scrDim.width;
pHeight = scrDim.height;
setBackground(Color.white);
setPreferredSize(scrDim);
setFocusable(true);
requestFocus(); // the JPanel now has focus, so receives key events
readyForTermination();
obs = new Obstacles(this);
fred = new Worm(pWidth, pHeight, obs);
addMouseListener( new MouseAdapter() {
public void mousePressed(MouseEvent e)
{ testPress(e.getX(), e.getY()); }
});
addMouseMotionListener( new MouseMotionAdapter() {
public void mouseMoved(MouseEvent e)
{ testMove(e.getX(), e.getY()); }
});
font = new Font("SansSerif", Font.BOLD, 24);
metrics = this.getFontMetrics(font);
pauseArea = new Rectangle(pWidth-100, pHeight-45, 70, 15); //Create buttons
quitArea = new Rectangle(pWidth-100, pHeight-20, 70, 15);
// Stats stuff
}
} // end of WormPanel()
32
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormPanel readyForTermination()
– addShutDownHook() (in readyForTermination()) is included in case thegame ends abnormally
∗ It insures that the statistics are displayed no matter what
private void readyForTermination()
{
addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent e)
{ int keyCode = e.getKeyCode();
if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q) ||
(keyCode == KeyEvent.VK_END) ||
((keyCode == KeyEvent.VK_C) && e.isControlDown()) ) {
running = false;
}
}
});
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run()
{
running = false;
// System.out.println("Shutdown hook executed");
finishOff();
}
});
} // end of readyForTermination()
public void addNotify()
{
super.addNotify(); // creates the peer
if (animator == null || !running) { // start the thread
animator = new Thread(this);
animator.start();
}
} // end of addNotify()
33
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormPanel testPress() and testMove()
– isOverPauseButton and isOverQuitButton are Boolean variables
private void testPress(int x, int y)
{
if (isOverPauseButton)
isPaused = !isPaused; // toggle pausing
else if (isOverQuitButton)
running = false;
else {
if (!isPaused && !gameOver) {
if (fred.nearHead(x,y)) { // was mouse pressed near the head?
gameOver = true;
score = (40 - timeSpentInGame) + (40 - boxesUsed);
}
else { // add an obstacle if possible
if (!fred.touchedAt(x,y)) // was the worm’s body untouched?
obs.add(x,y);
}
}
}
} // end of testPress()
private void testMove(int x, int y)
{
if (running) { // stops problems with a rapid move after pressing ’quit’
isOverPauseButton = pauseArea.contains(x,y) ? true : false;
isOverQuitButton = quitArea.contains(x,y) ? true : false;
}
}
public void setBoxNumber(int no)
{ boxesUsed = no; }
34
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormPanel run()
public void run()
{
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
Graphics g;
gameStartTime = J3DTimer.getValue();
prevStatsTime = gameStartTime;
beforeTime = gameStartTime;
running = true;
while(running) {
gameUpdate();
gameRender(); // render the game to a buffer
paintScreen(); // draw the buffer on-screen
afterTime = J3DTimer.getValue();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) { // some time left in this cycle
try {
Thread.sleep(sleepTime/1000000L); // nano -> ms
}
catch(InterruptedException ex){}
overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
}
else { // sleepTime <= 0; the frame took longer than the period
excess -= sleepTime; // store excess time value
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield(); // give another thread a chance to run
noDelays = 0;
}
}
beforeTime = J3DTimer.getValue();
int skips = 0;
while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
excess -= period;
gameUpdate(); // update state but don’t render
skips++;
}
framesSkipped += skips;
// excess = excess % period;
storeStats();
}
finishOff();
} // end of run()
private void gameUpdate()
{ if (!isPaused && !gameOver)
fred.move();
} // end of gameUpdate()
35
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormPanel gameRender()
– Note that gameRender() now includes a call to drawButtons()
– drawButtons() handles drawing the buttons appropriately, based on whetherthe mouse is positioned over the button or not
private void gameRender()
{
if (dbImage == null){
dbImage = createImage(pWidth, pHeight);
if (dbImage == null) {
System.out.println("dbImage is null");
return;
}
else
dbg = dbImage.getGraphics();
}
dbg.setColor(Color.white);
dbg.fillRect(0, 0, pWidth, pHeight);
dbg.setColor(Color.blue);
dbg.setFont(font);
dbg.drawString("Average FPS/UPS: " + df.format(averageFPS) + ", " +
df.format(averageUPS), 20, 25);
dbg.drawString("Time Spent: " + timeSpentInGame + " secs", 10, pHeight-15);
dbg.drawString("Boxes used: " + boxesUsed, 260, pHeight-15);
drawButtons(dbg);
dbg.setColor(Color.black);
obs.draw(dbg);
fred.draw(dbg);
if (gameOver)
gameOverMessage(dbg);
} // end of gameRender()
private void drawButtons(Graphics g)
{
g.setColor(Color.black);
if (isOverPauseButton)
g.setColor(Color.green);
g.drawOval( pauseArea.x, pauseArea.y, pauseArea.width, pauseArea.height);
if (isPaused)
g.drawString("Paused", pauseArea.x, pauseArea.y+10);
else
g.drawString("Pause", pauseArea.x+5, pauseArea.y+10);
if (isOverPauseButton)
g.setColor(Color.black);
if (isOverQuitButton)
g.setColor(Color.green);
g.drawOval(quitArea.x, quitArea.y, quitArea.width, quitArea.height);
g.drawString("Quit", quitArea.x+15, quitArea.y+10);
if (isOverQuitButton)
g.setColor(Color.black);
} // drawButtons()
36
Chapters 3 and 4: Game Display Modes - Undecorated Full Screen Mode,WormPanel Rest
private void gameOverMessage(Graphics g)
{
String msg = "Game Over. Your Score: " + score;
int x = (pWidth - metrics.stringWidth(msg))/2;
int y = (pHeight - metrics.getHeight())/2;
g.setColor(Color.red);
g.setFont(font);
g.drawString(msg, x, y);
} // end of gameOverMessage()
private void paintScreen()
{
Graphics g;
try {
g = this.getGraphics();
if ((g != null) && (dbImage != null))
g.drawImage(dbImage, 0, 0, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
catch (Exception e) // quite commonly seen at applet destruction
{ System.out.println("Graphics error: " + e); }
} // end of paintScreen()
// private void storeStats()
private void finishOff()
{
if (!finishedOff) {
finishedOff = true;
printStats();
System.exit(0);
}
} // end of finishedOff()
// private void printStats()
} // end of WormPanel class
37
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode
• Full screen exclusive mode gives the programmer almost direct access to thescreen
• It bypasses most of AWT and Swing access in favor of accessing the graphicscard’s capabilities
• This mode uses VRAM, which may be grabbed by the OS
– This means that the image buffer must be re-created on each iteration
• Hardware acceleration primarily a Windows feature
– Neither Linux nor Solaris provide direct access to VRAM
• Class diagram:
• Note that WormPanel has been incorporated into WormChase, since gameevents are now handled elsewhere, leaving little for WormChase to do
– The undecorated mode could have been coded this way as well, since that’swhere the functionality disappeared originally
38
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase constructor
• WormChase class
– Note that addNotify() is replaced by gameStart() (last line of constructor)
– Code:import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.text.DecimalFormat;
import java.awt.image.BufferStrategy;
import com.sun.j3d.utils.timer.J3DTimer;
public class WormChase extends JFrame implements Runnable
{
//Lots of variable declarations
public WormChase(long period)
{
super("Worm Chase");
this.period = period;
initFullScreen();
readyForTermination();
obs = new Obstacles(this);
fred = new Worm(pWidth, pHeight, obs);
addMouseListener( new MouseAdapter() {
public void mousePressed(MouseEvent e)
{ testPress(e.getX(), e.getY()); }
});
addMouseMotionListener( new MouseMotionAdapter() {
public void mouseMoved(MouseEvent e)
{ testMove(e.getX(), e.getY()); }
});
font = new Font("SansSerif", Font.BOLD, 24);
metrics = this.getFontMetrics(font);
pauseArea = new Rectangle(pWidth-100, pHeight-45, 70, 15);
quitArea = new Rectangle(pWidth-100, pHeight-20, 70, 15);
// Stats stuff
// ...
}
gameStart();
} // end of WormChase()
39
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase initFullScreen()
– initFullScreen() sets up full screen exclusive mode
∗ getLocalGraphicsEnvironment() returns a GraphicsEnvironment() objectthat represents a 2D environment that is available to the Java VM
· This environment consists of the available screens and fonts
· The screens are represented by GraphicsDevice objects
· getDefaultScreenDevice() returns the default device
∗ GraphicsDevice gd provides access to the graphics card
∗ If full screen exclusive mode is not available, the program quits
· It should more properly switch to another mode
private void initFullScreen()
{
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gd = ge.getDefaultScreenDevice();
setUndecorated(true); // no menu bar, borders, etc. or Swing components
setIgnoreRepaint(true); // turn off all paint events since doing active rendering
setResizable(false);
if (!gd.isFullScreenSupported()) {
System.out.println("Full-screen exclusive mode not supported");
System.exit(0);
}
gd.setFullScreenWindow(this); // switch on full-screen exclusive mode
showCurrentMode();
// setDisplayMode(1280, 1024, 32);
// reportCapabilities();
pWidth = getBounds().width;
pHeight = getBounds().height;
setBufferStrategy();
} // end of initFullScreen()
40
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase reportCapabilities() and getFlipText()
– reportCapabilities() queries the capabilities of the graphics device
∗ Class GraphicsConfiguration() represents the properties of a graphicsenvironment (color models supported, buffers, etc.)
∗ getFlipText() indicates how page flipping (a form of double buffering -see below) is handled
private void reportCapabilities()
{
GraphicsConfiguration gc = gd.getDefaultConfiguration();
ImageCapabilities imageCaps = gc.getImageCapabilities();
System.out.println("Image Caps. isAccelerated: " + imageCaps.isAccelerated() );
System.out.println("Image Caps. isTrueVolatile: " + imageCaps.isTrueVolatile());
BufferCapabilities bufferCaps = gc.getBufferCapabilities();
System.out.println("Buffer Caps. isPageFlipping: " + bufferCaps.isPageFlipping());
System.out.println("Buffer Caps. Flip Contents: " +
getFlipText(bufferCaps.getFlipContents()));
System.out.println("Buffer Caps. Full-screen Required: " +
bufferCaps.isFullScreenRequired());
System.out.println("Buffer Caps. MultiBuffers: " + bufferCaps.isMultiBufferAvailable());
} // end of reportCapabilities()
private String getFlipText(BufferCapabilities.FlipContents flip)
{
if (flip == null)
return "false";
else if (flip == BufferCapabilities.FlipContents.UNDEFINED)
return "Undefined";
else if (flip == BufferCapabilities.FlipContents.BACKGROUND)
return "Background";
else if (flip == BufferCapabilities.FlipContents.PRIOR)
return "Prior";
else // if (flip == BufferCapabilities.FlipContents.COPIED)
return "Copied";
} // end of getFlipTest()
41
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase setBufferStrategy()
– setBufferStrategy() sets up the buffer strategy
∗ invokeAndWait() starts a thread and waits until after the thread returns
∗ Note: A thread is used here to prevent a deadlock situation that mayoccur prior to J2SE5.0
– createBufferStrategy() (inherited from class Canvas) selects the buffer strat-egy
∗ It tries the following strategies in order:
1. Page flipping
2. Accelerated copy
3. Non-accelerated copy
∗ Page flipping is anther way to implement double buffering
· As usual, two buffers are used
· In this approach
1. Buffer A is displayed
2. Buffer B is written to
3. When drawing is complete, the video pointer is set to B
4. Now, A will be written to while B is displayed
· This is much faster than copying one buffer to anotherprivate void setBufferStrategy()
{ try {
EventQueue.invokeAndWait( new Runnable() {
public void run()
{ createBufferStrategy(NUM_BUFFERS); }
});
}
catch (Exception e) {
System.out.println("Error while creating buffer strategy");
System.exit(0);
}
try { // sleep to give time for the buffer strategy to be carried out
Thread.sleep(500); // 0.5 sec
}
catch(InterruptedException ex){}
bufferStrategy = getBufferStrategy(); // store for later
} // end of setBufferStrategy()
// private void readyForTermination()
// private void gameStart()
// private void testPress(int x, int y)
// private void testMove(int x, int y)
// public void setBoxNumber(int no)
42
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase run()
– run(): Nothing new here
public void run()
{
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
gameStartTime = J3DTimer.getValue();
prevStatsTime = gameStartTime;
beforeTime = gameStartTime;
running = true;
while(running) {
gameUpdate();
screenUpdate();
afterTime = J3DTimer.getValue();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) { // some time left in this cycle
try {
Thread.sleep(sleepTime/1000000L); // nano -> ms
}
catch(InterruptedException ex){}
overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
}
else { // sleepTime <= 0; the frame took longer than the period
excess -= sleepTime; // store excess time value
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield(); // give another thread a chance to run
noDelays = 0;
}
}
beforeTime = J3DTimer.getValue();
int skips = 0;
while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
excess -= period;
gameUpdate(); // update state but don’t render
skips++;
}
framesSkipped += skips;
storeStats();
}
finishOff();
} // end of run()
// private void gameUpdate()
43
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase screenUpdate()
– screenUpdate()
∗ bufferStrategy.getDrawGraphics() retrieves a graphics context for draw-ing
∗ The buffer strategy initiated by initFullScreen() (and implemented bycreateBufferStrategy()) is used here
∗ the if/else statement checks to see whether the OS has pre-empted theVRAM
· If it has, the contents have been lost and need to be redrawn (whichwill be taken care of on the next iteration)
private void screenUpdate()
// use active rendering
{
try {
gScr = bufferStrategy.getDrawGraphics();
gameRender(gScr);
gScr.dispose();
if (!bufferStrategy.contentsLost())
bufferStrategy.show();
else
System.out.println("Contents Lost");
// Sync the display on some systems.
// (on Linux, this fixes event queue problems)
Toolkit.getDefaultToolkit().sync();
}
catch (Exception e)
{ e.printStackTrace();
running = false;
}
} // end of screenUpdate()
44
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase gameRender()
– gameRender() same as before
∗ No need for Image object, as bufferStrategy() supplies it as a parameter
private void gameRender(Graphics gScr)
{
// clear the background
gScr.setColor(Color.white);
gScr.fillRect (0, 0, pWidth, pHeight);
gScr.setColor(Color.blue);
gScr.setFont(font);
// report frame count & average FPS and UPS at top left
// gScr.drawString("Frame Count " + frameCount, 10, 25);
gScr.drawString("Average FPS/UPS: " + df.format(averageFPS) + ", " +
df.format(averageUPS), 20, 25); // was (10,55)
// report time used and boxes used at bottom left
gScr.drawString("Time Spent: " + timeSpentInGame + " secs", 10, pHeight-15);
gScr.drawString("Boxes used: " + boxesUsed, 260, pHeight-15);
// draw the pause and quit ’buttons’
drawButtons(gScr);
gScr.setColor(Color.black);
// draw game elements: the obstacles and the worm
obs.draw(gScr);
fred.draw(gScr);
if (gameOver)
gameOverMessage(gScr);
} // end of gameRender()
45
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode,WormChase restoreScreen()
– Quitting the game same as before
∗ restoreScreen() called to exit full screen exclusive mode
// private void drawButtons(Graphics g)
// private void gameOverMessage(Graphics g)
// private void storeStats()
// private void finishOff()
// private void printStats()
private void restoreScreen()
/* Switch off full screen mode. This also resets the
display mode if it’s been changed.
*/
{ Window w = gd.getFullScreenWindow();
if (w != null)
w.dispose();
gd.setFullScreenWindow(null);
} // end of restoreScreen()
46
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode
– The display mode
∗ Setting the display mode can increase performance
1. Using a lower resolution will speed up double buffering (if page flip-ping not used)
2. Using the same raster depth and data representation as the displaydevice will prevent the need to convert between representations
∗ getDisplayMode() determines the mode of the display device
∗ setDisplayMode()
· Checks to see if the display mode can be changed
· If so, it checks to see if the given mode is supported
// ------------------ display mode methods -------------------
private void setDisplayMode(int width, int height, int bitDepth)
{
if (!gd.isDisplayChangeSupported()) {
System.out.println("Display mode changing not supported");
return;
}
if (!isDisplayModeAvailable(width, height, bitDepth)) {
System.out.println("Display mode (" + width + "," +
height + "," + bitDepth + ") not available");
return;
}
DisplayMode dm = new DisplayMode(width, height, bitDepth,
DisplayMode.REFRESH_RATE_UNKNOWN); // any refresh rate
try {
gd.setDisplayMode(dm);
System.out.println("Display mode set to: (" + width + "," +
height + "," + bitDepth + ")");
}
catch (IllegalArgumentException e)
{ System.out.println("Error setting Display mode (" + width + "," +
height + "," + bitDepth + ")"); }
try { // sleep to give time for the display to be changed
Thread.sleep(1000); // 1 sec
}
catch(InterruptedException ex){}
} // end of setDisplayMode()
47
Chapters 3 and 4: Game Display Modes - Full Screen Exclusive Mode (2)
private boolean isDisplayModeAvailable(int width, int height, int bitDepth)
{
DisplayMode[] modes = gd.getDisplayModes();
showModes(modes);
for(int i = 0; i < modes.length; i++) {
if (width == modes[i].getWidth() && height == modes[i].getHeight() &&
bitDepth == modes[i].getBitDepth())
return true;
}
return false;
} // end of isDisplayModeAvailable()
private void showModes(DisplayMode[] modes)
// pretty print the display mode information in modes
{
System.out.println("Modes");
for(int i = 0; i < modes.length; i++) {
System.out.print("(" + modes[i].getWidth() + "," +
modes[i].getHeight() + "," +
modes[i].getBitDepth() + "," +
modes[i].getRefreshRate() + ") " );
if ((i+1)%4 == 0)
System.out.println();
}
System.out.println();
} // end of showModes()
private void showCurrentMode()
{
DisplayMode dm = gd.getDisplayMode();
System.out.println("Current Display Mode: (" +
dm.getWidth() + "," + dm.getHeight() + "," +
dm.getBitDepth() + "," + dm.getRefreshRate() + ") " );
}
public static void main(String args[])
{
int fps = DEFAULT_FPS;
if (args.length != 0)
fps = Integer.parseInt(args[0]);
long period = (long) 1000.0/fps;
System.out.println("fps: " + fps + "; period: " + period + " ms");
new WormChase(period*1000000L); // ms --> nanosecs
} // end of main()
} // end of WormChase class
48