我正在尝试制作一个印在 Android 开发人员书中的大炮游戏,但在绘制实际游戏时遇到了麻烦。我可以听到我的声音,在我的计时器用完后显示游戏结束时的警报对话框,即使我看不到计时器,并且显示白色背景,只是没有游戏元素
以下是主要活动:
//CannonGame.java
//Main Activity for the Cannon game app.
package com.deitel.cannongame;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.media.AudioManager;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.SimpleOnGestureListener;
public class CannonGame extends Activity {
private GestureDetector gestureDetector; //listens for double taps
private CannonView cannonView; //custom view to display the game
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //call super's onCreate method
setContentView(R.layout.main); //inflate the layout
//get the cannonview
cannonView = (CannonView) findViewById(R.id.cannonView);
//initialize the GuestureDetector
gestureDetector = new GestureDetector(this, gestureListener);
//allow for volume keys to set game volume
setVolumeControlStream(AudioManager.STREAM_MUSIC);
} //end method onCreate
//when the app is pushed to the background, pause it
@Override
public void onPause(){
super.onPause(); //call the super method
cannonView.stopGame(); //terminates the game
} //end method onPause
//release resources
@Override
protected void onDestroy(){
super.onDestroy();
cannonView.releaseResources();
} //end method onDestroy
//called when the user touches the screen in this Activity
@Override
public boolean onTouchEvent(MotionEvent event){
//get int representing the type of action which caused this event
int action = event.getAction();
//the user touched the screen or dragged aong the screen
if (action == (MotionEvent.ACTION_DOWN) || action == MotionEvent.ACTION_MOVE)
cannonView.alignCannon(event); //align the cannon
//call the GuestureDetector's onTouchEvent method
return gestureDetector.onTouchEvent(event);
} //end onTouchEvent
//listens for touch events sent to the GuestureDetector
SimpleOnGestureListener gestureListener = new SimpleOnGestureListener(){
//called when the use doubletaps the screen
@Override
public boolean onDoubleTap(MotionEvent e){
cannonView.fireCannonball(e); //fire the cannonball
return true; //the event was handled
} //end method onDoubleTap
}; //end gestureListener
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.cannon_game, menu);
return true;
}
}
这是主要活动调用的自定义视图及其所有属性:
//CannonView.java
//Displays the Cannon Game
package com.deitel.cannongame;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Paint;
import android.media.SoundPool;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CannonView extends SurfaceView implements SurfaceHolder.Callback{
private CannonThread cannonThread; //controls the game loop
private Activity activity; //to display Game Over dialog in GUI thread
private boolean dialogIsDisplayed = false;
//constants for game play
public static final int TARGET_PIECES = 7; //sections in the target
public static final int MISS_PENALTY = 2; //seconds deducted on a miss
public static final int HIT_REWARD = 3; //seconds added on a hit
//variables for the game loop and tracking statistics
private boolean gameOver; //is the game over?
private double timeLeft; //the amount of time left in seconds
private int shotsFired; //the number of shots the user has fired
private double totalTimeElapsed; //the number of seconds elapsed
//variables for the blocker and target
private Line blocker; //start and end points of the blocker
private int blockerDistance; //blocker distance from the left
private int blockerBeginning; //blocker distance from the top
private int blockerEnd; //blocker bottom edge distance from the top
private int initialBlockerVelocity; //initial blocker speed multiplier
private float blockerVelocity; //blocker speed multiplier during game
private Line target; //start and end points of the target
private int targetDistance; //target distance from left
private int targetBeginning; //target distance from the top
private double pieceLength; //length of target piece
private int targetEnd; //target bottom distance from the top
private int initialTargetVelocity; //initial target speed multiplier
private float targetVelocity; //target speed multiplier during game
private int lineWidth; //width of the target and blocker
private boolean[] hitStates; //is each target piece hit?
private int targetPiecesHit; //number of target pieces hit (out of 7)
//variables for the cannon and cannonball
private Point cannonball; //cannonball image's upper-left corner
private int cannonballVelocityX; //cannonball's x velocity
private int cannonballVelocityY; //cannonball's y velocity
private boolean cannonballOnScreen; //is the cannonball on the screen
private int cannonballRadius; //cannonball radius
private int cannonballSpeed; //cannonball speed
private int cannonBaseRadius; //cannon base radius
private int cannonLength; //cannon barrel length
private Point barrelEnd; //the endpoint of the cannon's barrel
private int screenWidth; //width of the screen
private int screenHeight; //height ofthe screen
//constants and variables for managing sounds
private static final int TARGET_SOUND_ID = 0;
private static final int CANNON_SOUND_ID = 1;
private static final int BLOCKER_SOUND_ID = 2;
private SoundPool soundPool; //plays sound effects
private Map<Integer, Integer> soundMap; //maps IDs to SoundPool
//Paint vairables used when drawing each item on the screen
private Paint textPaint; //paint used to draw text
private Paint cannonballPaint; //paint used to draw the cannonball
private Paint cannonPaint; //paint used to draw the cannon
private Paint blockerPaint; //paint used to draw the blocker
private Paint targetPaint; //paint used to draw the target
private Paint backgroundPaint; //paint used to clear the drawing area
//public constructor
public CannonView(Context context, AttributeSet attrs){
super(context, attrs); //call super's constructor
activity = (Activity) context;
//register SurfaceHolder.Calback listener
getHolder().addCallback(this);
//initiate Lines and points representing game items
blocker = new Line(); //create the blocker as a Line
target = new Line(); //create the target as a Line
cannonball = new Point(); //create the cannonball as a point
//initialize hitStates as a boolean array
hitStates = new boolean[TARGET_PIECES];
//initialize SoundPool to play the app's three sound effects
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
//create Map of sounds and pre-load sounds
soundMap = new HashMap<Integer, Integer>(); //create new HashMap
soundMap.put(TARGET_SOUND_ID, soundPool.load(context, R.raw.target_hit, 1));
soundMap.put(CANNON_SOUND_ID, soundPool.load(context, R.raw.cannon_fire, 1));
soundMap.put(BLOCKER_SOUND_ID, soundPool.load(context, R.raw.blocker_hit, 1));
//construct Paints for drawing text, cannonball, cannon, blocker, and target; these are configured
//in method onSizeChanged
textPaint = new Paint(); //paint for drawing text
cannonballPaint = new Paint(); //paint for drawing the cannonball
cannonPaint = new Paint(); //paint for drawing the cannon
blockerPaint = new Paint(); //Paint for drawing the blocker
targetPaint = new Paint(); //Paint for drawing the target
backgroundPaint = new Paint(); //paint for drawing the background
} //end CannonView constructor
//called when the size of this view changes --including when this view is first added to the view hierarchy
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
screenWidth = w; //store the width
screenHeight = h; //store the height
cannonBaseRadius = h / 18; //cannon base radius 1/18 screen height
cannonLength = w / 8; //cannon length 1/8 screen width
cannonballRadius = w / 36; //cannonball radious 1/36 screen width
cannonballSpeed = w * 3 / 2; //cannonball speed multiplier
lineWidth = w / 24; //target and blocker 1/24 screen width
//configure instance variables related to the blocker
blockerDistance = w * 5 / 8; //blocker 5/8 screen width from left
blockerBeginning = h / 8; //distance from top 1/8 screen height
blockerEnd = h * 3 / 8; //distance from top 3/8 screen height
initialBlockerVelocity = h / 2; //initial blocker speed multiplier
blocker.start = new Point(blockerDistance, blockerBeginning);
blocker.end = new Point(blockerDistance, blockerEnd);
//configure instance variables related to the target
targetDistance = w * 7 / 8; //target 7/8 screen width from left
targetBeginning = h / 8; //distance from top 1/8 screen height
targetEnd = h * 7 / 8; //distance from top 7/8 screen height
pieceLength = (targetEnd - targetBeginning) / TARGET_PIECES;
initialTargetVelocity = -h / 4; //initial target speed multiplier
target.start = new Point(targetDistance, targetBeginning);
target.end = new Point(targetDistance, targetEnd);
//endpoint of the cannon's barrel initially points horizontally
barrelEnd = new Point(cannonLength, h / 2);
//configure Paint objects for drawing game elements
textPaint.setTextSize(w / 20); //text size 1/20 of screen width
textPaint.setAntiAlias(true); //smoothes the text
cannonPaint.setStrokeWidth(lineWidth * 1.5f); //set line thickness
blockerPaint.setStrokeWidth(lineWidth); //set line thickness
targetPaint.setStrokeWidth(lineWidth); //set line thickness
backgroundPaint.setColor(Color.WHITE); //set background color
newGame(); //set up and start a new game
} //end method onSizeChange
public void newGame(){
//set every element of hitStates to false--restores target pieces
for (int i = 0; i < TARGET_PIECES; ++i)
hitStates[i] = false;
targetPiecesHit = 0; //no target pieces have been hit
blockerVelocity = initialBlockerVelocity; //set initial blocker velocity
targetVelocity = initialTargetVelocity; //set initial target velocity
timeLeft = 10; //start the countdown at 10 seconds
cannonballOnScreen = false; //the cannonball is not on the screen
shotsFired = 0; //set the initial number of shots fired
totalTimeElapsed = 0.0; //set the time elapsed to zero
blocker.start.set(blockerDistance, blockerEnd);
blocker.end.set(blockerDistance, blockerEnd);
target.start.set(targetDistance, targetBeginning);
target.end.set(targetDistance, targetEnd);
if (gameOver){
gameOver = false; //the game is not over
cannonThread = new CannonThread(getHolder());
cannonThread.start();
} //end if
} //end method newGame
//called repleatedly by the CannonThread to update the game elements
private void updatePositions(double elapsedtimeMS){
double interval = elapsedtimeMS / 1000.0; //convert to seconds
if(cannonballOnScreen){ //if there is currently a shot fired
//update cannonball position
cannonball.x += interval * cannonballVelocityX;
cannonball.y += interval * cannonballVelocityY;
//check for collision with blocker
if(cannonball.x + cannonballRadius > blockerDistance && cannonball.x - cannonballRadius
< blockerDistance && cannonball.y + cannonballRadius > blocker.start.y && cannonball.y - cannonballRadius < blocker.end.y){
cannonballVelocityX *= -1; //reverse cannonball's direction
timeLeft -= MISS_PENALTY; //penalize the user
//play blocker sound
soundPool.play(soundMap.get(BLOCKER_SOUND_ID), 1, 1, 1, 0, 1f);
} //end if
//check for collisions with left and right walls
else if(cannonball.x + cannonballRadius > screenWidth || cannonball.x - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.y + cannonballRadius > screenHeight || cannonball.y - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.x + cannonballRadius > targetDistance && cannonball.x - cannonballRadius <
targetDistance && cannonball.y + cannonballRadius > target.start.y && cannonball.y - cannonballRadius < target.end.y){
//determine target section number (0 is the top)
int section = (int) ((cannonball.y - target.start.y) / pieceLength);
//check if the piece hasn't been hit yet
if((section >= 0 && section < TARGET_PIECES) && !hitStates[section]){
hitStates[section] = true; //section was hit
cannonballOnScreen = false; //remove cannonball
timeLeft += HIT_REWARD; //add reward to remaining time
//play target hit sound
soundPool.play(soundMap.get(TARGET_SOUND_ID), 1, 1, 1, 0, 1F);
//if all pieces have been hit
if (++targetPiecesHit == TARGET_PIECES){
cannonThread.setRunning(false); //show winning dialog
gameOver = true; //the game is over
} //end if
} //end if
} //end else if
} //end if
//update the blockers position
double blockerUpdate = interval * blockerVelocity;
blocker.start.y += blockerUpdate;
blocker.end.y += blockerUpdate;
//update the target's position
double targetUpdate = interval * targetVelocity;
target.start.y += targetUpdate;
target.end.y += targetUpdate;
//if the blocker hit the top or bottom, reverse direction
if(blocker.start.y < 0 || blocker.end.y > screenHeight)
blockerVelocity *= -1;
//if the target hit the top or bottom, reverse direction
if(target.start.y < 0 || target.end.y > screenHeight)
targetVelocity *= -1;
timeLeft -= interval; //subtract from time left
//if the trimer reached zero
if(timeLeft <= 0){
timeLeft = 0.0;
gameOver = true; //the game is over
cannonThread.setRunning(false);
showGameOverDialog(R.string.lose); //show the losing dialog
} //end if
} //end method updatePositions
//fires a cannonball
public void fireCannonball(MotionEvent event){
if(cannonballOnScreen) //if a cannonball is already on the screen
return; //do nothing
double angle = alignCannon(event); //get the cannon barrel's angle
//move the cannonball to be inside the cannon
cannonball.x = cannonballRadius; //align x-coordinate with cannon
cannonball.y = screenHeight / 2; //centers ball vertically
//get the x component of the total velocity
cannonballVelocityX = (int) (cannonballSpeed * Math.sin(angle));
//get the y component of the total velocity
cannonballVelocityY = (int)(-cannonballSpeed * Math.cos(angle));
cannonballOnScreen = true; //the cannonball is on the screen
++shotsFired; //increment shots fired
//play cannon fired sound
soundPool.play(soundMap.get(CANNON_SOUND_ID), 1, 1, 1, 0, 1f);
} //end method fireCannonball
//aligns the cannon in response to a user touch
public double alignCannon(MotionEvent event){
//get the location of the touch in this view
Point touchPoint = new Point((int) event.getX(), (int) event.getY());
//compute the touch's distance from the center of the screen on the Y axis
double centerMinusY = (screenHeight / 2 - touchPoint.y);
double angle = 0; //initialize angle to 0
//calculate the angle the barrel makes with the horizontal
if(centerMinusY != 0) //prevent division by 0
angle = Math.atan((double) touchPoint.x / centerMinusY);
//if the touch is on the lower half of the screen
if(touchPoint.y > screenHeight / 2)
angle += Math.PI; //adjust the angle
//calculate the endpoint of the cannon barrel
barrelEnd.x = (int) (cannonLength * Math.sin(angle));
barrelEnd.y = (int) (-cannonLength * Math.cos(angle) + screenHeight / 2);
return angle; //return the computed angle
} //end method alignCannon
//draws the game to the given Canvas
public void drawGameElements(Canvas canvas){
//clear the background
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), backgroundPaint);
//display the time remaining
canvas.drawText(getResources().getString(R.string.time_remaining_format, timeLeft), 30, 50, textPaint);
//if a cannonball is currently on the screen, draw it
if(cannonballOnScreen)
canvas.drawCircle(cannonball.x, cannonball.y, cannonballRadius, cannonballPaint);
//draw the cannon barrel
canvas.drawLine(0, screenHeight / 2, barrelEnd.x, barrelEnd.y, cannonPaint);
//draw the cannon base
canvas.drawCircle(0, (int) screenHeight / 2, (int) cannonBaseRadius, cannonPaint);
//draw the blocker
canvas.drawLine(blocker.start.x, blocker.start.y, blocker.end.x, blocker.end.y, blockerPaint);
Point currentPoint = new Point(); //start of current target section
//initialize curPoint to the starting point of the target
currentPoint.x = target.start.x;
currentPoint.y = target.start.y;
//draw the target
for (int i = 1; i <= TARGET_PIECES; ++i){
//if this target piece is not hit, draw it
if(!hitStates[i - 1]){
//alternate coloring the pieces yellow and blue
if(i % 2 == 0)
targetPaint.setColor(Color.YELLOW);
else
targetPaint.setColor(Color.BLUE);
canvas.drawLine(currentPoint.x, currentPoint.y, target.end.x,
(int) (currentPoint.y + pieceLength), targetPaint);
} //end if
//move curPoint to the start of the next piece
currentPoint.y += pieceLength;
} //end for
} //end method drawGameElements
//display an AlertDialog when the game ends
private void showGameOverDialog(int messageId){
//create a dialog dislaying the given String
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
dialogBuilder.setTitle(getResources().getString(messageId));
dialogBuilder.setCancelable(false);
//display number of shots fired an total time elapsed
dialogBuilder.setMessage(getResources().getString(R.string.results_format, shotsFired, totalTimeElapsed));
dialogBuilder.setPositiveButton(R.string.reset_game,
new DialogInterface.OnClickListener() {
//called when "Reset Game" Button is pressed
@Override
public void onClick(DialogInterface dialog, int which) {
dialogIsDisplayed = false;
newGame(); //set up and start a new game
} //end method onClick
} //end anonymous inner class
); //end call to setPositiveButton
activity.runOnUiThread(
new Runnable() {
public void run(){
dialogIsDisplayed = true;
dialogBuilder.show(); //display the dialog
} //end run
}//end runnable
); //end call to runOnUiThread
} //end method showGameOverDialog
//pauses the game
public void stopGame(){
if(cannonThread != null)
cannonThread.setRunning(false);
} //end method stopGame
//releases reources; called by CannonGame's onDestroy method
public void releaseResources(){
soundPool.release(); //release all resource used by the SoundPool
soundPool = null;
} //end method releaseResources
//called when surface changes size
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
} //end method surfaceChanged
//called when surface is first created
@Override
public void surfaceCreated(SurfaceHolder holder){
cannonThread = new CannonThread(holder);
cannonThread.setRunning(true);
cannonThread.start(); //start the game loop thread
} //end method surfaceCreated
//called when surface is destroyed
@Override
public void surfaceDestroyed(SurfaceHolder holder){
//ensure that thread terminates properly
boolean retry = true;
cannonThread.setRunning(false);
while(retry){
try{
cannonThread.join();
retry = false;
} //end try
catch(InterruptedException e){
} //end catch
} //end while
} //end method surfaceDestroyed
//Thread subclass to control the game loop
private class CannonThread extends Thread{
private SurfaceHolder surfaceHolder; //for manipulating canvas
private boolean threadIsRunning = true; //running by default
//initialize the surface holder
public CannonThread(SurfaceHolder holder){
surfaceHolder = holder;
setName("CannonThread");
} //end constructor
//changes the running state
public void setRunning(boolean running){
threadIsRunning = running;
} //end method setRunning
//controls the game loop
@Override
public void run(){
Canvas canvas = null; //used for drawing
long previousFrameTime = System.currentTimeMillis();
while(threadIsRunning){
try{
canvas = surfaceHolder.lockCanvas(null);
//lock the surafceHolder for drawing
synchronized(surfaceHolder){
long currentTime = System.currentTimeMillis();
double elapsedTimeMS = currentTime - previousFrameTime;
totalTimeElapsed += elapsedTimeMS / 1000.0;
updatePositions(elapsedTimeMS); //update game stats
drawGameElements(canvas); //draw
previousFrameTime = currentTime; //updateprevious time
} //end synchronized block
} //end try
finally{
if(canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
} //end finally
} //end while
} //end method run
} //end nested class CannonThread
} //end class CannonView
最后,这是我的自定义视图使用的 xml 约束:
<?xml version="1.0" encoding="utf-8"?>
<com.deitel.cannongame.CannonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/cannonView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"/>
我应该提到我在他们的网站上尝试了这本书的示例文件,我遇到了完全相同的问题。