|
/*
DEVELOPING GAME IN JAVA
Caracteristiques
Editeur : NEW RIDERS
Auteur : BRACKEEN
Parution : 09 2003
Pages : 972
Isbn : 1-59273-005-1
Reliure : Paperback
Disponibilite : Disponible a la librairie
*/
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferUShort;
import java.awt.image.IndexColorModel;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class CollisionTest extends ShooterCore {
public static void main(String[] args) {
new CollisionTest(args).run();
}
protected BSPTree bspTree;
protected String mapFile;
public CollisionTest(String[] args) {
super(args);
for (int i = 0; mapFile == null && i < args.length; i++) {
if (mapFile == null && !args[i].startsWith("-")) {
mapFile = args[i];
}
}
if (mapFile == null) {
mapFile = "../images/sample.map";
}
}
public void createPolygons() {
Graphics2D g = screen.getGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, screen.getWidth(), screen.getHeight());
g.setColor(Color.WHITE);
g.drawString("Loading...", 5, screen.getHeight() - 5);
screen.update();
float ambientLightIntensity = .2f;
List lights = new LinkedList();
lights.add(new PointLight3D(-100, 100, 100, .3f, -1));
lights.add(new PointLight3D(100, 100, 0, .3f, -1));
MapLoader loader = new MapLoader();
loader.setObjectLights(lights, ambientLightIntensity);
try {
bspTree = loader.loadMap(mapFile);
} catch (IOException ex) {
ex.printStackTrace();
}
CollisionDetection collisionDetection = new CollisionDetection(bspTree);
gameObjectManager = new GridGameObjectManager(bspTree.calcBounds(),
collisionDetection);
gameObjectManager.addPlayer(new GameObject(new PolygonGroup("Player")));
// set up player bounds
PolygonGroupBounds playerBounds = gameObjectManager.getPlayer()
.getBounds();
playerBounds.setTopHeight(Player.DEFAULT_PLAYER_HEIGHT);
playerBounds.setRadius(Player.DEFAULT_PLAYER_RADIUS);
((BSPRenderer) polygonRenderer).setGameObjectManager(gameObjectManager);
createGameObjects(loader.getObjectsInMap());
Transform3D start = loader.getPlayerStartLocation();
gameObjectManager.getPlayer().getTransform().setTo(start);
}
private void createGameObjects(List mapObjects) {
Iterator i = mapObjects.iterator();
while (i.hasNext()) {
PolygonGroup group = (PolygonGroup) i.next();
String filename = group.getFilename();
if ("robot.obj".equals(filename)) {
gameObjectManager.add(new Bot(group));
} else {
// static object
gameObjectManager.add(new GameObject(group));
}
}
}
public void drawPolygons(Graphics2D g) {
polygonRenderer.startFrame(g);
// draw polygons in bsp tree (set z buffer)
((BSPRenderer) polygonRenderer).draw(g, bspTree);
// draw game object polygons (check and set z buffer)
gameObjectManager.draw(g, (GameObjectRenderer) polygonRenderer);
polygonRenderer.endFrame(g);
}
}
/**
* A GameObject that can jump.
*/
class JumpingGameObject extends GameObject {
public static final float DEFAULT_JUMP_HEIGHT = 64;
protected float jumpVelocity;
public JumpingGameObject(PolygonGroup group) {
super(group);
setJumpHeight(DEFAULT_JUMP_HEIGHT);
}
/**
* Sets how high this GameObject can jump.
*/
public void setJumpHeight(float jumpHeight) {
jumpVelocity = Physics.getInstance().getJumpVelocity(jumpHeight);
}
/**
* Causes this GameObject to jump if the jumping flag is set and this object
* is not already jumping.
*/
public void setJumping(boolean isJumping) {
if (isJumping() != isJumping) {
super.setJumping(isJumping);
if (isJumping) {
Physics.getInstance().jump(this, jumpVelocity);
}
}
}
public void notifyFloorCollision() {
// the object has landed.
setJumping(false);
}
}
/**
* The Physics class is a singleton that represents various attributes (like
* gravity) and the functions to manipulate objects based on those physical
* attributes. Currently, only gravity and scoot-up (acceleration when traveling
* up stairs) are supported.
*/
class Physics {
/**
* Default gravity in units per millisecond squared
*/
public static final float DEFAULT_GRAVITY_ACCEL = -.002f;
/**
* Default scoot-up (acceleration traveling up stairs) in units per
* millisecond squared.
*/
public static final float DEFAULT_SCOOT_ACCEL = .006f;
private static Physics instance;
private float gravityAccel;
private float scootAccel;
private Vector3D velocity = new Vector3D();
/**
* Gets the Physics instance. If a Physics instance does not yet exist, one
* is created with the default attributes.
*/
public static synchronized Physics getInstance() {
if (instance == null) {
instance = new Physics();
}
return instance;
}
protected Physics() {
gravityAccel = DEFAULT_GRAVITY_ACCEL;
scootAccel = DEFAULT_SCOOT_ACCEL;
}
/**
* Gets the gravity acceleration in units per millisecond squared.
*/
public float getGravityAccel() {
return gravityAccel;
}
/**
* Sets the gravity acceleration in units per millisecond squared.
*/
public void setGravityAccel(float gravityAccel) {
this.gravityAccel = gravityAccel;
}
/**
* Gets the scoot-up acceleration in units per millisecond squared. The
* scoot up acceleration can be used for smoothly traveling up stairs.
*/
public float getScootAccel() {
return scootAccel;
}
/**
* Sets the scoot-up acceleration in units per millisecond squared. The
* scoot up acceleration can be used for smoothly traveling up stairs.
*/
public void setScootAccel(float scootAccel) {
this.scootAccel = scootAccel;
}
/**
* Applies gravity to the specified GameObject according to the amount of
* time that has passed.
*/
public void applyGravity(GameObject object, long elapsedTime) {
velocity.setTo(0, gravityAccel * elapsedTime, 0);
object.getTransform().addVelocity(velocity);
}
/**
* Applies the scoot-up acceleration to the specified GameObject according
* to the amount of time that has passed.
*/
public void scootUp(GameObject object, long elapsedTime) {
velocity.setTo(0, scootAccel * elapsedTime, 0);
object.getTransform().addVelocity(velocity);
}
/**
* Applies the negative scoot-up acceleration to the specified GameObject
* according to the amount of time that has passed.
*/
public void scootDown(GameObject object, long elapsedTime) {
velocity.setTo(0, -scootAccel * elapsedTime, 0);
object.getTransform().addVelocity(velocity);
}
/**
* Sets the specified GameObject's vertical velocity to jump to the
* specified height. Calls getJumpVelocity() to calculate the velocity,
* which uses the Math.sqrt() function.
*/
public void jumpToHeight(GameObject object, float jumpHeight) {
jump(object, getJumpVelocity(jumpHeight));
}
/**
* Sets the specified GameObject's vertical velocity to the specified jump
* velocity.
*/
public void jump(GameObject object, float jumpVelocity) {
velocity.setTo(0, jumpVelocity, 0);
object.getTransform().getVelocity().y = 0;
object.getTransform().addVelocity(velocity);
}
/**
* Returns the vertical velocity needed to jump the specified height (based
* on current gravity). Uses the Math.sqrt() function.
*/
public float getJumpVelocity(float jumpHeight) {
// use velocity/acceleration formal: v*v = -2 * a(y-y0)
// (v is jump velocity, a is accel, y-y0 is max height)
return (float) Math.sqrt(-2 * gravityAccel * jumpHeight);
}
}
/**
* A Player object.
*/
class Player extends JumpingGameObject {
public static final float DEFAULT_PLAYER_RADIUS = 32;
public static final float DEFAULT_PLAYER_HEIGHT = 128;
public Player() {
this(new PolygonGroup("Player"));
// set up player bounds
PolygonGroupBounds playerBounds = getBounds();
playerBounds.setTopHeight(DEFAULT_PLAYER_HEIGHT);
playerBounds.setRadius(DEFAULT_PLAYER_RADIUS);
}
public Player(PolygonGroup group) {
super(group);
}
}
/**
* The GridGameObjectManager is a GameObjectManager that integrally arranges
* GameObjects on a 2D grid for visibility determination and to limit the number
* of tests for collision detection.
*/
class GridGameObjectManager implements GameObjectManager {
/**
* Default grid size of 512. The grid size should be larger than the largest
* object's diameter.
*/
private static final int GRID_SIZE_BITS = 9;
private static final int GRID_SIZE = 1 << GRID_SIZE_BITS;
/**
* The Cell class represents a cell in the grid. It contains a list of game
* objects and a visible flag.
*/
private static class Cell {
List objects;
boolean visible;
Cell() {
objects = new ArrayList();
visible = false;
}
}
private Cell[] grid;
private Rectangle mapBounds;
private int gridWidth;
private int gridHeight;
private List allObjects;
private GameObject player;
private Vector3D oldLocation;
private CollisionDetection collisionDetection;
/**
* Creates a new GridGameObjectManager with the specified map bounds and
* collision detection handler. GameObjects outside the map bounds will
* never be shown.
*/
public GridGameObjectManager(Rectangle mapBounds,
CollisionDetection collisionDetection) {
this.mapBounds = mapBounds;
this.collisionDetection = collisionDetection;
gridWidth = (mapBounds.width >> GRID_SIZE_BITS) + 1;
gridHeight = (mapBounds.height >> GRID_SIZE_BITS) + 1;
grid = new Cell[gridWidth * gridHeight];
for (int i = 0; i < grid.length; i++) {
grid[i] = new Cell();
}
allObjects = new ArrayList();
oldLocation = new Vector3D();
}
/**
* Converts a map x-coordinate to a grid x-coordinate.
*/
private int convertMapXtoGridX(int x) {
return (x - mapBounds.x) >> GRID_SIZE_BITS;
}
/**
* Converts a map y-coordinate to a grid y-coordinate.
*/
private int convertMapYtoGridY(int y) {
return (y - mapBounds.y) >> GRID_SIZE_BITS;
}
/**
* Marks all objects as potentially visible (should be drawn).
*/
public void markAllVisible() {
for (int i = 0; i < grid.length; i++) {
grid[i].visible = true;
}
}
/**
* Marks all objects within the specified 2D bounds as potentially visible
* (should be drawn).
*/
public void markVisible(Rectangle bounds) {
int x1 = Math.max(0, convertMapXtoGridX(bounds.x));
int y1 = Math.max(0, convertMapYtoGridY(bounds.y));
int x2 = Math.min(gridWidth - 1, convertMapXtoGridX(bounds.x
+ bounds.width));
int y2 = Math.min(gridHeight - 1, convertMapYtoGridY(bounds.y
+ bounds.height));
for (int y = y1; y <= y2; y++) {
int offset = y * gridWidth;
for (int x = x1; x <= x2; x++) {
grid[offset + x].visible = true;
}
}
}
/**
* Adds a GameObject to this manager.
*/
public void add(GameObject object) {
if (object != null) {
if (object == player) {
// ensure player always moves first
allObjects.add(0, object);
} else {
allObjects.add(object);
}
Cell cell = getCell(object);
if (cell != null) {
cell.objects.add(object);
}
}
}
/**
* Removes a GameObject from this manager.
*/
public void remove(GameObject object) {
if (object != null) {
allObjects.remove(object);
Cell cell = getCell(object);
if (cell != null) {
cell.objects.remove(object);
}
}
}
/**
* Adds a GameObject to this manager, specifying it as the player object. An
* existing player object, if any, is not removed.
*/
public void addPlayer(GameObject player) {
this.player = player;
if (player != null) {
player.notifyVisible(true);
add(player);
}
}
/**
* Gets the object specified as the Player object, or null if no player
* object was specified.
*/
public GameObject getPlayer() {
return player;
}
/**
* Gets the cell the specified GameObject is in, or null if the GameObject
* is not within the map bounds.
*/
private Cell getCell(GameObject object) {
int x = convertMapXtoGridX((int) object.getX());
int y = convertMapYtoGridY((int) object.getZ());
return getCell(x, y);
}
/**
* Gets the cell of the specified grid location, or null if the grid
* location is invalid.
*/
private Cell getCell(int x, int y) {
// check bounds
if (x < 0 || y < 0 || x >= gridWidth || y >= gridHeight) {
return null;
}
// get the cell at the x,y location
return grid[x + y * gridWidth];
}
/**
* Updates all objects based on the amount of time passed from the last
* update and applied collision detection.
*/
public void update(long elapsedTime) {
for (int i = 0; i < allObjects.size(); i++) {
GameObject object = (GameObject) allObjects.get(i);
// save the object's old position
Cell oldCell = getCell(object);
oldLocation.setTo(object.getLocation());
// move the object
object.update(player, elapsedTime);
// remove the object if destroyed
if (object.isDestroyed()) {
allObjects.remove(i);
i--;
if (oldCell != null) {
oldCell.objects.remove(object);
}
continue;
}
// if the object moved, do collision detection
if (!object.getLocation().equals(oldLocation)) {
// check walls, floors, and ceilings
collisionDetection.checkBSP(object, oldLocation, elapsedTime);
// check other objects
if (checkObjectCollision(object, oldLocation)) {
// revert to old position
object.getLocation().setTo(oldLocation);
}
// update grid location
Cell cell = getCell(object);
if (cell != oldCell) {
if (oldCell != null) {
oldCell.objects.remove(object);
}
if (cell != null) {
cell.objects.add(object);
}
}
}
}
}
/**
* Checks to see if the specified object collides with any other object.
*/
public boolean checkObjectCollision(GameObject object, Vector3D oldLocation) {
boolean collision = false;
// use the object's (x,z) position (ground plane)
int x = convertMapXtoGridX((int) object.getX());
int y = convertMapYtoGridY((int) object.getZ());
// check the object's surrounding 9 cells
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
Cell cell = getCell(i, j);
if (cell != null) {
collision |= collisionDetection.checkObject(object,
cell.objects, oldLocation);
}
}
}
return collision;
}
/**
* Draws all visible objects and marks all objects as not visible.
*/
public void draw(Graphics2D g, GameObjectRenderer r) {
for (int i = 0; i < grid.length; i++) {
List objects = grid[i].objects;
for (int j = 0; j < objects.size(); j++) {
GameObject object = (GameObject) objects.get(j);
boolean visible = false;
if (grid[i].visible) {
visible = r.draw(g, object);
}
if (object != player) {
// notify objects if they are visible
object.notifyVisible(visible);
}
}
grid[i].visible = false;
}
}
}
/**
* The CollisionDetection class handles collision detection between the
* GameObjects, and between GameObjects and a BSP tree. When a collision occurs,
* the GameObject stops.
*/
class CollisionDetection {
/**
* Bounding game object corners used to test for intersection with the BSP
* tree. Corners are in either clockwise or counter-clockwise order.
*/
private static final Point2D.Float[] CORNERS = { new Point2D.Float(-1, -1),
new Point2D.Float(-1, 1), new Point2D.Float(1, 1),
new Point2D.Float(1, -1), };
private BSPTree bspTree;
private BSPLine path;
private Point2D.Float intersection;
/**
* Creates a new CollisionDetection object for the specified BSP tree.
*/
public CollisionDetection(BSPTree bspTree) {
this.bspTree = bspTree;
path = new BSPLine();
intersection = new Point2D.Float();
}
/**
* Checks a GameObject against the BSP tree. Returns true if a wall
* collision occurred.
*/
public boolean checkBSP(GameObject object, Vector3D oldLocation,
long elapsedTime) {
boolean wallCollision = false;
// check walls if x or z position changed
if (object.getX() != oldLocation.x || object.getZ() != oldLocation.z) {
wallCollision = (checkWalls(object, oldLocation, elapsedTime) != null);
}
getFloorAndCeiling(object);
checkFloorAndCeiling(object, elapsedTime);
return wallCollision;
}
/**
* Gets the floor and ceiling values for the specified GameObject. Calls
* object.setFloorHeight() and object.setCeilHeight() to set the floor and
* ceiling values.
*/
public void getFloorAndCeiling(GameObject object) {
float x = object.getX();
float z = object.getZ();
float r = object.getBounds().getRadius() - 1;
float floorHeight = Float.MIN_VALUE;
float ceilHeight = Float.MAX_VALUE;
BSPTree.Leaf leaf = bspTree.getLeaf(x, z);
if (leaf != null) {
floorHeight = leaf.floorHeight;
ceilHeight = leaf.ceilHeight;
}
// check surrounding four points
for (int i = 0; i < CORNERS.length; i++) {
float xOffset = r * CORNERS[i].x;
float zOffset = r * CORNERS[i].y;
leaf = bspTree.getLeaf(x + xOffset, z + zOffset);
if (leaf != null) {
floorHeight = Math.max(floorHeight, leaf.floorHeight);
ceilHeight = Math.min(ceilHeight, leaf.ceilHeight);
}
}
object.setFloorHeight(floorHeight);
object.setCeilHeight(ceilHeight
|