本节书摘来异步社区《Java 2D游戏编程入门》一书中的第8章,第8.7节,作者:【美】Timothy Wright(莱特),更多章节内容可以访问云栖社区“异步社区”公众号查看。
8.7 编写原型游戏
原型游戏如图8.12所示,位于javagames.prototype包中,它使用了我们目前为止所见过的所有技术。尽管这只是一个原型,并且目前还没有成为一款完整的游戏,但我已经展示了足够的工具来让一些功能奏效。如果要等到最后再制作一款游戏,可能需要等太长的时间。
该原型游戏使用了我们在本章前面所介绍的如下的类。
- PolygonWrapper
- PrototypeShip
- PrototypeAsteroid
- PrototypeAsteroidFactory
- PrototypeBullet
当你在本章末尾尝试编译和运行代码之前,确保已经创建了这些类。
initialize()方法为原型创建了所有这些对象,包括创建了一些星星作为背景。如下的代码创建了星星,并且创建了颜色的一个数组。Color类接受3个值:red、blue和green,这3个值在0到1之间。将每种颜色值设置为相同的值,将会产生灰色的阴影。本书稍后会详细介绍颜色,但现在,只要使用Java语言所提供的java.awt.Color类就行了。
// PrototypeGame.java
private void createStars() {
stars = new Vector2f[ STAR_COUNT ];
colors = new Color[ STAR_COUNT ];
for( int i = 0; i < stars.length; ++i ) {
float x = rand.nextFloat() * 2.0f - 1.0f;
float y = rand.nextFloat() * 2.0f - 1.0f;
stars[i] = new Vector2f( x, y );
float color = rand.nextFloat();
colors[i] = new Color( color, color, color );
}
}```
当创建小行星的时候,注意getAsteroidStartPosition()方法。和产生各种大小的随机小行星的示例不同,这个方法只创建一个较大的随机小行星,并且使用如下代码将其放置到一个圆圈中,以使得它们不会在飞船之上产生。
// PrototypeGame.java
private Vector2f getAsteroidStartPosition() {
float angle = (float)Math.toRadians( rand.nextInt( 360 ) );
float minimum = appWorldWidth / 4.0f;
float extra = rand.nextFloat() * minimum;
float radius = minimum + extra;
return Vector2f.polar( angle, radius );
}`
前面的代码在圆圈中放置了新的多边形,该圆圈是屏幕的四分之一大,如图8.13所示。
这会防止这种现象发生:新产生的小行星出现于飞船的顶部,玩家还没来得及开火,就碰到它并爆炸了。这是游戏程序员所面对的各种挑战的一个很好的例子。只有在原型游戏开始运行时,这个问题才会变得明显。不管游戏多么简单,总是会有奇怪的问题需要解决。
processInput()方法使用向左键和向右键来旋转飞船,向上键会激活加速动作,空格键会发射子弹,Escape键会重新产生小行星。
当子弹击中小行星时,不仅小行星会从渲染列表中删除,而且如果小行星不是太小的话,它会分裂成两块更小的小行星。
updateShip()方法检查碰撞。如果飞船被击中,会设置毁灭标志。尽管在真实的游戏中,当飞船被击中时,游戏会重新启动,但我们还没有介绍玩家生命或游戏结束状态的概念,因此,目前当飞船被击中时,将其绘制为红色。
render()方法绘制星星、所有的小行星、子弹、飞船以及常用的帧速率和指令。还有一些新的代码,它们会开启抗锯齿功能,以使线条绘制得更为平滑。第10章将会介绍抗锯齿。
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);```
drawStars()、drawShip()、drawAsteroids()和drawBullets()方法负责绘制原型中的各种物体。PrototypeGame的代码如下所示:
package javagames.prototype;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.*;
import javagames.prototype.PrototypeAsteroid.Size;
import javagames.util.*;
public class PrototypeGame extends SimpleFramework {
private static final int STAR_COUNT = 1500;
private PrototypeShip ship;
private PolygonWrapper wrapper;
private PrototypeAsteroidFactory factory;
private ArrayList bullets;
private ArrayList asteroids;
private Random rand;
private Vector2f[] stars;
private Color[] colors;
public PrototypeGame() {
appBorderScale = 0.9f;
appWidth = 640;
appHeight = 640;
appMaintainRatio = true;
appSleep = 1L;
appTitle = "Prototype Game";
}
@Override
protected void initialize() {
super.initialize();
// create game objects
rand = new Random();
bullets = new ArrayList();
asteroids = new ArrayList();
wrapper = new PolygonWrapper( appWorldWidth, appWorldHeight );
ship = new PrototypeShip( wrapper );
factory = new PrototypeAsteroidFactory( wrapper );
createStars();
createAsteroids();
}
// this creates the random stars for the background
private void createStars() {
stars = new Vector2f[ STAR_COUNT ];
colors = new Color[ STAR_COUNT ];
for( int i = 0; i < stars.length; ++i ) {
float x = rand.nextFloat() * 2.0f - 1.0f;
float y = rand.nextFloat() * 2.0f - 1.0f;
stars[i] = new Vector2f( x, y );
float color = rand.nextFloat();
colors[i] = new Color( color, color, color );
}
}
// create the random asteroids
private void createAsteroids() {
asteroids.clear();
for( int i = 0; i < 4; ++i ) {
Vector2f position = getAsteroidStartPosition();
asteroids.add( factory.createLargeAsteroid( position ) );
}
}
// create random position for an asteroid
private Vector2f getAsteroidStartPosition() {
float angle = (float)Math.toRadians( rand.nextInt( 360 ) );
float minimum = appWorldWidth / 4.0f;
float extra = rand.nextFloat() * minimum;
float radius = minimum + extra;
return Vector2f.polar( angle, radius );
}
@Override
protected void processInput( float delta ) {
super.processInput( delta );
// fly the ship
if( keyboard.keyDown( KeyEvent.VK_LEFT ) ) {
ship.rotateLeft( delta );
}
if( keyboard.keyDown( KeyEvent.VK_RIGHT ) ) {
ship.rotateRight( delta );
}
if( keyboard.keyDownOnce( KeyEvent.VK_SPACE ) ) {
bullets.add( ship.launchBullet() );
}
if( keyboard.keyDownOnce( KeyEvent.VK_ESCAPE ) ) {
createAsteroids();
}
ship.setThrusting( keyboard.keyDown( KeyEvent.VK_UP ) );
}
@Override
protected void updateObjects( float delta ) {
super.updateObjects( delta );
updateAsteroids( delta );
updateBullets( delta );
updateShip( delta );
}
private void updateAsteroids( float delta ) {
for( PrototypeAsteroid asteroid : asteroids ) {
asteroid.update( delta );
}
}
private void updateBullets( float delta ) {
ArrayList copy =
new ArrayList( bullets );
for( PrototypeBullet bullet : copy ) {
updateBullet( delta, bullet );
}
}
// check for bullet collisions
private void updateBullet( float delta, PrototypeBullet bullet ) {
bullet.update( delta );
if( wrapper.hasLeftWorld( bullet.getPosition() ) ) {
bullets.remove( bullet );
} else {
ArrayList ast =
new ArrayList( asteroids );
for( PrototypeAsteroid asteroid : ast ) {
if( asteroid.contains( bullet.getPosition() ) ) {
bullets.remove( bullet );
asteroids.remove( asteroid );
spawnBabies( asteroid );
}
}
}
}
// create smaller asteroids when one is broken apart
private void spawnBabies( PrototypeAsteroid asteroid ) {
if( asteroid.getSize() == Size.Large ) {
asteroids.add(
factory.createMediumAsteroid( asteroid.getPosition() ) );
asteroids.add(
factory.createMediumAsteroid( asteroid.getPosition() ) );
}
if( asteroid.getSize() == Size.Medium ) {
asteroids.add(
factory.createSmallAsteroid( asteroid.getPosition() ) );
asteroids.add(
factory.createSmallAsteroid( asteroid.getPosition() ) );
}
}
// update the ship object
private void updateShip( float delta ) {
ship.update( delta );
boolean isHit = false;
for( PrototypeAsteroid asteroid : asteroids ) {
if( ship.isTouching( asteroid ) ) {
isHit = true;
}
}
ship.setDamaged( isHit );
}
@Override
protected void render( Graphics g ) {
// render instructions
super.render( g );
g.drawString( "Rotate: Left/Right Arrow", 20, 35 );
g.drawString( "Thrust: Up Arrow", 20, 50 );
g.drawString( "Fire: Space Bar", 20, 65 );
g.drawString( "Press ESC to respawn", 20, 80 );
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
// draw game objects
Matrix3x3f view = getViewportTransform();
drawStars( g2d, view );
drawAsteroids( g2d, view );
drawBullets( g2d, view );
drawShip( g2d, view );
}
private void drawStars( Graphics2D g, Matrix3x3f view ) {
for( int i = 0; i < stars.length; ++i ) {
g.setColor( colors[i] );
Vector2f screen = view.mul( stars[i] );
g.fillRect( (int)screen.x, (int)screen.y, 1, 1 );
}
}
private void drawShip( Graphics2D g, Matrix3x3f view ) {
ship.draw( g, view );
}
private void drawAsteroids( Graphics2D g, Matrix3x3f view ) {
for( PrototypeAsteroid asteroid : asteroids ) {
asteroid.draw( g, view );
}
}
private void drawBullets( Graphics2D g, Matrix3x3f view ) {
for( PrototypeBullet b : bullets ) {
b.draw( g, view );
}
}
public static void main( String[] args ) {
launchApp( new PrototypeGame() );
}
}`