?? gamemanager.java
字號:
// Copyright 2003 Nokia Corporation.
//
// THIS SOURCE CODE IS PROVIDED 'AS IS', WITH NO WARRANTIES WHATSOEVER,
// EXPRESS OR IMPLIED, INCLUDING ANY WARRANTY OF MERCHANTABILITY, FITNESS
// FOR ANY PARTICULAR PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE
// OR TRADE PRACTICE, RELATING TO THE SOURCE CODE OR ANY WARRANTY OTHERWISE
// ARISING OUT OF ANY PROPOSAL, SPECIFICATION, OR SAMPLE AND WITH NO
// OBLIGATION OF NOKIA TO PROVIDE THE LICENSEE WITH ANY MAINTENANCE OR
// SUPPORT. FURTHERMORE, NOKIA MAKES NO WARRANTY THAT EXERCISE OF THE
// RIGHTS GRANTED HEREUNDER DOES NOT INFRINGE OR MAY NOT CAUSE INFRINGEMENT
// OF ANY PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS OWNED OR CONTROLLED
// BY THIRD PARTIES
//
// Furthermore, information provided in this source code is preliminary,
// and may be changed substantially prior to final release. Nokia Corporation
// retains the right to make changes to this source code at
// any time, without notice. This source code is provided for informational
// purposes only.
//
// Nokia and Nokia Connecting People are registered trademarks of Nokia
// Corporation.
// Java and all Java-based marks are trademarks or registered trademarks of
// Sun Microsystems, Inc.
// Other product and company names mentioned herein may be trademarks or
// trade names of their respective owners.
//
// A non-exclusive, non-transferable, worldwide, limited license is hereby
// granted to the Licensee to download, print, reproduce and modify the
// source code. The licensee has the right to market, sell, distribute and
// make available the source code in original or modified form only when
// incorporated into the programs developed by the Licensee. No other
// license, express or implied, by estoppel or otherwise, to any other
// intellectual property rights is granted herein.
import java.util.Vector;
import javax.microedition.lcdui.*;
// GameManager is used by CloseableCanvas or NokiaCloseableCanvas.
// (BlockGameMIDlet creates a CloseableCanvas or NokiaCloseableCanvas
// depending on the capabilities of the MIDP device where the MIDlet
// was downloaded.)
//
// This GameManager tries to be as portable as possible. It doesn't
// use any drawing feature of the Nokia UI API's FullCanvas that an
// ordinary MIDP Canvas API doesn't support, other than the basic
// 'full canvas' property of FullCanvas.
class GameManager
implements Runnable
{
// When either side gets GAME_OVER_SCORE points, the game ends.
final static int GAME_OVER_SCORE = 100;
final static int MILLIS_PER_TICK = 250; // msec
final static int MAX_CHANNELS = 8; // 8 channels of blocks
final static int MAX_BLOCK_HEIGHT = 10; // 10 pixels
final static int MAX_PLAYER_LIVES = 5; // 5 lives
private final static int MAX_PIXELS = 200;
private final static Font GAME_FONT =
Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
private final int gameWidth;
private final int gameHeight;
private final BlockGameMIDlet midlet;
private final Dictionary dict;
private final Canvas canvas;
private final Vector blocks = new Vector();
private final Base base;
private final DoublyLinkedList bullets = new DoublyLinkedList();
private final GameEffects gameEffects;
private final boolean useLimitedFiringRate;
private volatile Thread animationThread = null;
// hasBeenShown could be set by Canvas.showNotify()
private boolean hasBeenShown = false;
private volatile boolean isPaused = false;
private boolean isGameOver = false;
private int gameOverTicks = 0;
private int baseScore = 0;
private int blocksScore = 0;
// The text labels for the player's score and the base's 'lives count',
// take up a bit too much screen space on small displays. As the label
// text itself is rather static, we only show the label text for
// 'showLabelTicks' (7.5 seconds). Similarly, the base's 'lives count'
// also changes slowly.
private int showLabelTicks = (7500 / GameManager.MILLIS_PER_TICK);
GameManager(BlockGameMIDlet midlet, Dictionary dict,
GameEffects gameEffects, Canvas canvas)
{
this.midlet = midlet;
this.dict = dict;
this.canvas = canvas;
this.gameEffects = gameEffects;
useLimitedFiringRate = midlet.useLimitedFiringRate();
// Limit the maximum size of the game to be 200 x 200 pixels.
// This is for better game play on MIDP devices with a larger
// resolution. (For example on devices which are very much wider
// than long, it otherwise takes too long for the blocks to fly from
// right to left, and for the bullets to fly from left to right.)
gameWidth = (canvas.getWidth() < MAX_PIXELS) ?
gameHeight = (canvas.getHeight() < MAX_PIXELS) ?
canvas.getHeight() : MAX_PIXELS;
// A block is a square that is 'dimension' pixels high
// and 'dimension' pixels wide. The dimension of a block
// is one of the basic properties which affects how the game
// looks. (Note: the base's size also depends on this parameter
// as we'd like the base and blocks to be about the same size.)
//
// The screen height is divided into 'MAX_CHANNELS' or less channels.
// The blocks fly left down the channels. The following figure
// helps to illustrate:
// --------------------------------------------
// yOffset | ^
// +---+ ^ | channelHeight
// + + | blockHeight |
// +---+ v |
// v
// ----------------------------------------------
// There is one block per channel.
int numChannels = MAX_CHANNELS;
if (gameHeight < gameWidth)
{
numChannels = (gameHeight * numChannels) / gameWidth;
}
int channelHeight = gameHeight / numChannels;
// Set the blockHeight to be 70 % of channelHeight
// to leave some space between blocks.
int blockHeight = ((70 * channelHeight) / 100);
int yOffset = (channelHeight - blockHeight) / 2;
if ((numChannels < MAX_CHANNELS) &&
(blockHeight < MAX_BLOCK_HEIGHT))
{
// For smaller screens, the blockHeight (dimension) should
// be at least some minimum pixel size, otherwise the blocks
// and base feel too small.
// channel height equals block height
blockHeight = MAX_BLOCK_HEIGHT;
numChannels = gameHeight / blockHeight;
yOffset = 0;
}
Block.setDimension(blockHeight); // set dimension for all Blocks
// Picking a dx value that is independent of gameWidth + blockWidth
// and that works well for a variety of screen sizes is a bit tricky,
// because for the smallest device screen widths the game uses
// a minimum pixel size (which may also reduce the number
// of channels) rather than a constant ratio of blockWidth to
// gameWidth for all screen sizes. Also, on taller screens
// the user may have to spend more time moving up and down
// than on shorter screens (e.g. if there are 8 channels of blocks
// rather than 6 channels on a smaller device). This is result of not
// making the game scale proportional to the height and width of
// the screen, for all possible screen sizes.
//
// The block speed should not be either annoyingly fast or slow
// on a range of real devices.
int dx;
if (numChannels < MAX_CHANNELS)
{
dx = -(gameWidth / (3 * Block.getDimension()));
}
else
{
dx = -(gameWidth / (4 * Block.getDimension()));
}
if (dx == 0)
{
dx = -1;
}
for (int channel = 0; channel < numChannels; channel++)
{
Block block = new Block(this, 0, gameWidth,
(yOffset + (channel * channelHeight)), dx);
blocks.addElement(block);
}
int playerLives = MAX_PLAYER_LIVES;
base = new Base(this, useLimitedFiringRate, playerLives, 0, 0,
gameWidth, gameHeight);
}
boolean isGameOver()
{
return isGameOver;
}
private boolean baseIsWinning()
{
return ((base.getLives() > 0) && (baseScore >= blocksScore));
}
private void tick()
{
if (isGameOver)
{
// 1) The game is over.
// When the game is over, wait 30 seconds before stopping the
// animation thread. This gives the draw method a chance to wait
// a few seconds before displaying a message that prompts the user
// to press a softkey to return to the MainMenu. It also gives the
// 'game over' tune some time to play.)
if (gameOverTicks < (30000 / MILLIS_PER_TICK))
{
gameOverTicks++;
// When the game is over, wait 1 second before playing
// the game over music
if (gameEffects.hasSoundCapability() &&
(gameOverTicks == (1000 / MILLIS_PER_TICK)))
{
boolean hasPlayerWon = (baseScore >= blocksScore);
gameEffects.playGameOverMusic(hasPlayerWon);
}
}
else
{
// 30 seconds has passed, stop the animation thread, etc.
stop();
}
}
else if ((base.getLives() == 0) || (baseScore >= GAME_OVER_SCORE) ||
(blocksScore >= GAME_OVER_SCORE))
{
// 2) Detect and set the 'game over' state.
// Setting isGameOver to 'true' causes the 'if (isGameOver)'
// block of code above, to be executed on subsequent ticks.
isGameOver = true;
gameOverTicks = 0;
}
else
{
// 3) The game is still playing.
// showLabelTicks is used by the draw method to print
// longer or shorter text messages indicating the current score,
// lives, etc. When the game first starts, longer versions
// (with explanatory labels) are printed. When the tick
// counts reach zero, shorter versions are printed
// (so more of the screen is visible during playing of the game).
if (showLabelTicks > 0)
{
showLabelTicks--;
}
// Base tick
base.tick();
// Bullets' ticks
Bullet b = (Bullet) (bullets.getFirst());
Bullet prev = null;
while (b != null)
{
b.tick();
prev = b;
b = (Bullet) (bullets.getNext(b));
if (!prev.isActive())
{
bullets.remove(prev);
}
}
// Blocks' ticks
for (int ix = 0; ix < blocks.size(); ix++)
{
Block block = (Block) blocks.elementAt(ix);
block.tick();
// Check for bullet collisions
b = (Bullet) (bullets.getFirst());
while (b != null)
{
if (block.isCollision(b))
{
if (block.doBulletCollision())
{
// true: the block exploded in the collision
baseScore += block.getPoints();
block.updateStrength(); // stronger next life
gameEffects.playBlockExplosion();
}
b.doExplode();
}
b = (Bullet) (bullets.getNext(b));
}
// Check for base collisions
if (block.isCollision(base))
{
int lives = base.getLives();
if (!base.isColliding())
{
// If the base is not already colliding with
// another block and is colliding with this block,
// then handle the base collision:
// - inform base of collision
// - get new 'base lives' count
// - use GameEffects for explode noise + vibrate
base.doCollision();
gameEffects.playBaseExplosion();
gameEffects.vibrate();
}
// Handle block collision:
// Blocks get more points for hitting the base,
// than vice versa. Blocks get extra points
// for completely destroying base (no lives left).
baseScore += block.getPoints();
if (lives == 0)
{
blocksScore += 20;
}
else
{
blocksScore += (2 * block.getPoints());
}
block.doExplode();
block.updateStrength(); // A stronger next life
}
}
}
}
// Canvas methods
public void paint(Graphics g)
{
if (isGameOver)
{
// Print an appropriate 'game over' message.
int color;
String winnerText;
if (baseIsWinning())
{
color = Base.COLOR;
winnerText = dict.getString(Dictionary.TEXT_GAME_YOU_WON);
}
else
{
color = Block.COLOR;
winnerText = dict.getString(Dictionary.TEXT_GAME_YOU_LOST);
}
String lastText = null; // last line's default message
if (gameOverTicks < (4000 / MILLIS_PER_TICK))
{
if (base.getLives() == 0)
{
lastText =
dict.getString(Dictionary.TEXT_GAME_BASE_DESTROYED);
}
}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -