本节书摘来异步社区《Java 2D游戏编程入门》一书中的第8章,第8.2节,作者:【美】Timothy Wright(莱特),更多章节内容可以访问云栖社区“异步社区”公众号查看。
8.2 创建一个原型小行星
PrototypeAsteroid类位于javagames.prototype包中,它表示一个穿越太空的陨石。在创建的时候,使用了一个随机的速率和旋转。Java的随机数生成器只能返回0到1之间的浮点数,因此,要创建在任意范围内分布的随机数,需要一些额外的步骤。例如,要返回-3到7之间的随机数,应按照如下步骤进行。
- 1.用最大值减去最小值,计算随机数之间的差距。
- 2.生成从0到1的一个随机浮点数。
- 3.将随机数乘以差距值。
- 4.通过加上最小值来迁移范围。
这些步骤听起来有些令人混淆,实际上并非如此。
private float getRandomFloat( float min, float max ) {
float rand = new Random().nextFloat();
return rand * (max - min) + min;
}```
要得到一个随机的整数,也可以采用相同的步骤。由于随机数生成器返回范围从0到计数值减去1的一个数字,因此该方法需要略作调整。
private float getRandomRadians( int minDegree, int maxDegree ) {
int rand = new Random().nextInt( maxDegree - minDegree + 1 );
return (float)Math.toRadians( rand + minDegree );
}`
getRandomRotationDelta()方法返回了(5,45)到(–5,–45)之间的一个角度(弧度表示),如图8.8所示。
private float getRandomRotationDelta() {
float radians = getRandomRadians( 5, 45 );
return new Random().nextBoolean()? radians : -radians;
}```
还可以使用setter和getter方法来访问小行星的某些属性。
public void setPolygon( Vector2f[] polygon );
public void setPosition( Vector2f position );
public Vector2f getPosition();
public void setSize( PrototypeAsteroid.Size size );
public PrototypeAster.Size getSize()`
大小是一个枚举类型的值:
public class PrototypeAsteroid {
public enum Size {
Large,
Medium,
Small;
}
//...
}```
update()方法负责调整小行星的位置和旋转。注意,除了调整位置和旋转,PolygonWrapper类用来折返多边形的位置。
draw()方法负责用给定的视口矩阵和Graphics对象绘制多边形。要绘制填充的多边形,需要给Utility类添加两个方法:
package javagames.util;
import java.awt.*;
import java.util.List;
public class Utility {
// ... Other methods left out
// ... New methods are below
public static void fillPolygon( Graphics2D g, Vector2f[] polygon ) {
Polygon p = new Polygon();
for( Vector2f v : polygon ) {
p.addPoint( (int)v.x, (int)v.y );
}
g.fill( p );
}
public static void fillPolygon( Graphics2D g, List polygon ) {
Polygon p = new Polygon();
for( Vector2f v : polygon ) {
p.addPoint( (int)v.x, (int)v.y );
}
g.fill( p );
}
}`
pointInPolygon()方法和前面所讨论的方法相同。contains()方法接受一个点,如果PolygonWrapper所复制的任何多边形包含该点的话,它返回true。注意,PrototypeAsteroid中没有实际的模型代码。那是编辑器和工厂的工作,我们将在后面介绍。
package javagames.prototype;
import java.awt.*;
import java.util.*;
import javagames.util.*;
public class PrototypeAsteroid {
public enum Size {
Large,
Medium,
Small;
}
private PolygonWrapper wrapper;
private Size size;
private float rotation;
private float rotationDelta;
private Vector2f[] polygon;
private Vector2f position;
private Vector2f velocity;
private ArrayList<Vector2f[]> renderList;
public PrototypeAsteroid( PolygonWrapper wrapper ) {
this.wrapper = wrapper;
renderList = new ArrayList<Vector2f[]>();
velocity = getRandomVelocity();
rotationDelta = getRandomRotationDelta();
}
private Vector2f getRandomVelocity() {
float angle = getRandomRadians( 0, 360 );
float radius = getRandomFloat( 0.06f, 0.3f );
return Vector2f.polar( angle, radius );
}
private float getRandomRadians( int minDegree, int maxDegree ) {
int rand = new Random().nextInt( maxDegree - minDegree + 1 );
return (float)Math.toRadians( rand + minDegree );
}
private float getRandomRotationDelta() {
float radians = getRandomRadians( 5, 45 );
return new Random().nextBoolean() ? radians : -radians;
}
private float getRandomFloat( float min, float max ) {
float rand = new Random().nextFloat();
return rand * (max - min) + min;
}
public void setPolygon( Vector2f[] polygon ) {
this.polygon = polygon;
}
public void setPosition( Vector2f position ) {
this.position = position;
}
public Vector2f getPosition() {
return position;
}
public void setSize( Size size ) {
this.size = size;
}
public Size getSize() {
return size;
}
public void update( float time ) {
position = position.add( velocity.mul( time ) );
position = wrapper.wrapPosition( position );
rotation += rotationDelta * time;
renderList.clear();
Vector2f[] world = transformPolygon();
renderList.add( world );
wrapper.wrapPolygon( world, renderList );
}
private Vector2f[] transformPolygon() {
Matrix3x3f mat = Matrix3x3f.rotate( rotation );
mat = mat.mul( Matrix3x3f.translate( position ) );
return transform( polygon, mat );
}
private Vector2f[] transform( Vector2f[] poly, Matrix3x3f mat ) {
Vector2f[] copy = new Vector2f[ poly.length ];
for( int i = 0; i < poly.length; ++i ) {
copy[i] = mat.mul( poly[i] );
}
return copy;
}
public void draw( Graphics2D g, Matrix3x3f view ) {
for( Vector2f[] poly : renderList ) {
for( int i = 0; i < poly.length; ++i ) {
poly[i] = view.mul( poly[i] );
}
g.setColor( Color.LIGHT_GRAY );
Utility.fillPolygon( g, poly );
g.setColor( Color.BLACK );
Utility.drawPolygon( g, poly );
}
}
public boolean contains( Vector2f point ) {
for( Vector2f[] polygon : renderList ) {
if( pointInPolygon( point, polygon ) ) {
return true;
}
}
return false;
}
private boolean pointInPolygon( Vector2f point, Vector2f[] polygon ) {
boolean inside = false;
Vector2f start = polygon[ polygon.length - 1 ];
boolean startAbove = start.y >= point.y;
for( int i = 0; i < polygon.length; ++i ) {
Vector2f end = polygon[i];
boolean endAbove = end.y >= point.y;
if( startAbove != endAbove ) {
float m = (end.y - start.y) / (end.x - start.x);
float x = start.x + (point.y - start.y) / m;
if( x >= point.x ) {
inside = !inside;
}
}
startAbove = endAbove;
start = end;
}
return inside;
}