This is a very simple extension of HelloPhysics in the JME3 tutorial. When you click on a brick, the brick is removed from the game and the physicsspace. If you remove the bricks fast enough, before the wall settles, it crumbles like you would expect, but if you wait a little bit before removing bricks, nothing happens at all.
I think the physics turn off after things have stopped moving to prevent excessive jittering, and I want to manually start it up again.
package jme3test.helloworld;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
/**
* Example 12 - how to give objects physical properties so they bounce and fall.
* #author base code by double1984, updated by zathras
*/
class HelloPhysics extends SimpleApplication {
public static void main(String args[]) {
HelloPhysics app = new HelloPhysics();
app.start();
}
/** Prepare the Physics Application State (jBullet) */
private BulletAppState bulletAppState;
/** Prepare Materials */
Material wall_mat;
Material stone_mat;
Material floor_mat;
/** Prepare geometries and physical nodes for bricks and cannon balls. */
Node removables;
private RigidBodyControl brick_phy;
private static final Box box;
private RigidBodyControl ball_phy;
private static final Sphere sphere;
private RigidBodyControl floor_phy;
private static final Box floor;
/** dimensions used for bricks and wall */
private static final float brickLength = 0.48f;
private static final float brickWidth = 0.24f;
private static final float brickHeight = 0.12f;
static {
/** Initialize the cannon ball geometry */
sphere = new Sphere(32, 32, 0.4f, true, false);
sphere.setTextureMode(TextureMode.Projected);
/** Initialize the brick geometry */
box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
box.scaleTextureCoordinates(new Vector2f(1f, .5f));
/** Initialize the floor geometry */
floor = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
floor.scaleTextureCoordinates(new Vector2f(3, 6));
}
#Override
public void simpleInitApp() {
/** Set up Physics Game */
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);
/** Configure cam to look at scene */
cam.setLocation(new Vector3f(0, 4f, 6f));
cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
/** Add InputManager action: Left click triggers shooting. */
inputManager.addMapping("shoot",
new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(actionListener, "shoot");
removables = new Node("Removables");
rootNode.attachChild(removables);
/** Initialize the scene, materials, and physics space */
initMaterials();
initWall();
initFloor();
initKeys();
initCrossHairs();
}
/** Declaring the "Shoot" action and mapping to its triggers. */
private void initKeys() {
inputManager.addMapping("Shoot",
new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar
new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
inputManager.addListener(actionListener, "Shoot");
}
/** Initialize the materials used in this scene. */
public void initMaterials() {
wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
key.setGenerateMips(true);
Texture tex = assetManager.loadTexture(key);
wall_mat.setTexture("ColorMap", tex);
stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
key2.setGenerateMips(true);
Texture tex2 = assetManager.loadTexture(key2);
stone_mat.setTexture("ColorMap", tex2);
floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
key3.setGenerateMips(true);
Texture tex3 = assetManager.loadTexture(key3);
tex3.setWrap(WrapMode.Repeat);
floor_mat.setTexture("ColorMap", tex3);
}
/** Make a solid floor and add it to the scene. */
public void initFloor() {
Geometry floor_geo = new Geometry("Floor", floor);
floor_geo.setMaterial(floor_mat);
floor_geo.setLocalTranslation(0, -0.1f, 0);
this.rootNode.attachChild(floor_geo);
/* Make the floor physical with mass 0.0f! */
floor_phy = new RigidBodyControl(0.0f);
floor_geo.addControl(floor_phy);
bulletAppState.getPhysicsSpace().add(floor_phy);
}
/** This loop builds a wall out of individual bricks. */
public void initWall() {
float startpt = brickLength / 4;
float height = 0;
for (int j = 0; j < 15; j++) {
for (int i = 0; i < 6; i++) {
Vector3f vt =
new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
makeBrick(vt);
}
startpt = -startpt;
height += 2 * brickHeight;
}
}
/** This method creates one individual physical brick. */
public void makeBrick(Vector3f loc) {
/** Create a brick geometry and attach to scene graph. */
Geometry brick_geo = new Geometry("brick", box);
brick_geo.setMaterial(wall_mat);
/** Position the brick geometry */
brick_geo.setLocalTranslation(loc);
/** Make brick physical with a mass > 0.0f. */
brick_phy = new RigidBodyControl(2f);
/** Add physical brick to physics space. */
brick_geo.addControl(brick_phy);
bulletAppState.getPhysicsSpace().add(brick_phy);
removables.attachChild(brick_geo);
}
/** This method creates one individual physical cannon ball.
* By defaul, the ball is accelerated and flies
* from the camera position in the camera direction.*/
public void makeCannonBall() {
/** Create a cannon ball geometry and attach to scene graph. */
Geometry ball_geo = new Geometry("cannon ball", sphere);
ball_geo.setMaterial(stone_mat);
rootNode.attachChild(ball_geo);
/** Position the cannon ball */
ball_geo.setLocalTranslation(cam.getLocation());
/** Make the ball physcial with a mass > 0.0f */
ball_phy = new RigidBodyControl(1f);
/** Add physical ball to physics space. */
ball_geo.addControl(ball_phy);
bulletAppState.getPhysicsSpace().add(ball_phy);
/** Accelerate the physcial ball to shoot it. */
ball_phy.setLinearVelocity(cam.getDirection().mult(25));
}
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("Shoot") && !keyPressed) {
// 1. Reset results list.
CollisionResults results = new CollisionResults();
// 2. Aim the ray from cam loc to cam direction.
Ray ray = new Ray(cam.getLocation(), cam.getDirection());
// 3. Collect intersections between Ray and Shootables in results list.
removables.collideWith(ray, results);
// 4. Print the results
System.out.println("----- Collisions? " + results.size() + "-----");
for (int i = 0; i < results.size(); i++) {
// For each hit, we know distance, impact point, name of geometry.
float dist = results.getCollision(i).getDistance();
Vector3f pt = results.getCollision(i).getContactPoint();
String hit = results.getCollision(i).getGeometry().getName();
System.out.println("* Collision #" + i);
System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away.");
}
// 5. Use the results (we mark the hit object)
if (results.size() > 0) {
// The closest collision point is what was truly hit:
Geometry closest = results.getClosestCollision().getGeometry();
// Let's interact - we mark the hit with a red dot.
bulletAppState.getPhysicsSpace().remove(closest.getControl(0));
removables.detachChild(closest);
bulletAppState.getPhysicsSpace().clearForces();
bulletAppState.getPhysicsSpace().applyGravity();
System.out.println("removing a "+closest.getName());
}
}
}
};
/** A plus sign used as crosshairs to help the player with aiming.*/
protected void initCrossHairs() {
guiNode.detachAllChildren();
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
BitmapText ch = new BitmapText(guiFont, false);
ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
ch.setText("+"); // fake crosshairs :)
ch.setLocalTranslation( // center
settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
guiNode.attachChild(ch);
}
}
Objects which aren't moving sleep
Your initial thought was correct, when an object comes to rest it is put to sleep, this is for a number of reasons; avoiding jitter is one, but it also increases efficiency significantly.
While sleeping an object doesn't experience gravity, so if you remove the object supporting it it doesn't notice.
Usually you don't have to worry about this; if the object experiences an effect "internal to the physics engine" such as being involved with a collision it is automatically woken. However if you make external changes (such as removing an object) you have to worry about waking the object up.
Wake the objects that could have been affected by the external change
You can wake a sleeping object up by calling .activate() on the rigidBodyControl, remember sleep is done on a per object basis so you may need to wake more than 1.
At present the program does not keep a track of all the RigidBodyControl so I have added a HashSet<RigidBodyControl>() to hold them, when a brick is added to the scene it's rigidBodyControl should be added to this HashSet and when it is removed from the scene it should be removed from the hashset.
For an efficient solution you should only wake objects that could have been affected by the removal, but in this case it's probably everything anyway and I have simply woken everything by running through all of the HashSet<RigidBodyControl>() and calling activate() on them.
A complete program that demonstrates these changes is shown below
public class HelloPhysics extends SimpleApplication {
public static void main(String args[]) {
HelloPhysics app = new HelloPhysics();
app.start();
}
/** Prepare the Physics Application State (jBullet) */
private BulletAppState bulletAppState;
/** Prepare Materials */
Material wall_mat;
Material stone_mat;
Material floor_mat;
/** Prepare geometries and physical nodes for bricks and cannon balls. */
Node removables;
private static final Box box;
private RigidBodyControl ball_phy;
private static final Sphere sphere;
private RigidBodyControl floor_phy;
private static final Box floor;
private static Collection<RigidBodyControl> objectsThatNeedWaking=new HashSet<RigidBodyControl>();
/** dimensions used for bricks and wall */
private static final float brickLength = 0.48f;
private static final float brickWidth = 0.24f;
private static final float brickHeight = 0.12f;
static {
/** Initialize the cannon ball geometry */
sphere = new Sphere(32, 32, 0.4f, true, false);
sphere.setTextureMode(TextureMode.Projected);
/** Initialize the brick geometry */
box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
box.scaleTextureCoordinates(new Vector2f(1f, .5f));
/** Initialize the floor geometry */
floor = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
floor.scaleTextureCoordinates(new Vector2f(3, 6));
}
#Override
public void simpleInitApp() {
/** Set up Physics Game */
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);
/** Configure cam to look at scene */
cam.setLocation(new Vector3f(0, 4f, 6f));
cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
/** Add InputManager action: Left click triggers shooting. */
inputManager.addMapping("shoot",
new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(actionListener, "shoot");
removables = new Node("Removables");
rootNode.attachChild(removables);
/** Initialize the scene, materials, and physics space */
initMaterials();
initWall();
initFloor();
initKeys();
initCrossHairs();
}
/** Declaring the "Shoot" action and mapping to its triggers. */
private void initKeys() {
inputManager.addMapping("Shoot",
new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar
new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
inputManager.addListener(actionListener, "Shoot");
}
/** Initialize the materials used in this scene. */
public void initMaterials() {
wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
key.setGenerateMips(true);
Texture tex = assetManager.loadTexture(key);
wall_mat.setTexture("ColorMap", tex);
stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
key2.setGenerateMips(true);
Texture tex2 = assetManager.loadTexture(key2);
stone_mat.setTexture("ColorMap", tex2);
floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
key3.setGenerateMips(true);
Texture tex3 = assetManager.loadTexture(key3);
tex3.setWrap(WrapMode.Repeat);
floor_mat.setTexture("ColorMap", tex3);
}
/** Make a solid floor and add it to the scene. */
public void initFloor() {
Geometry floor_geo = new Geometry("Floor", floor);
floor_geo.setMaterial(floor_mat);
floor_geo.setLocalTranslation(0, -0.1f, 0);
this.rootNode.attachChild(floor_geo);
/* Make the floor physical with mass 0.0f! */
floor_phy = new RigidBodyControl(0.0f);
floor_geo.addControl(floor_phy);
bulletAppState.getPhysicsSpace().add(floor_phy);
}
/** This loop builds a wall out of individual bricks. */
public void initWall() {
float startpt = brickLength / 4;
float height = 0;
for (int j = 0; j < 15; j++) {
for (int i = 0; i < 6; i++) {
Vector3f vt =
new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
makeBrick(vt);
}
startpt = -startpt;
height += 2 * brickHeight;
}
}
/** This method creates one individual physical brick. */
public void makeBrick(Vector3f loc) {
/** Create a brick geometry and attach to scene graph. */
Geometry brick_geo = new Geometry("brick", box);
brick_geo.setMaterial(wall_mat);
/** Position the brick geometry */
brick_geo.setLocalTranslation(loc);
/** Make brick physical with a mass > 0.0f. */
RigidBodyControl brick_phy = new RigidBodyControl(2f);
objectsThatNeedWaking.add(brick_phy);
/** Add physical brick to physics space. */
brick_geo.addControl(brick_phy);
bulletAppState.getPhysicsSpace().add(brick_phy);
removables.attachChild(brick_geo);
}
/** This method creates one individual physical cannon ball.
* By defaul, the ball is accelerated and flies
* from the camera position in the camera direction.*/
public void makeCannonBall() {
/** Create a cannon ball geometry and attach to scene graph. */
Geometry ball_geo = new Geometry("cannon ball", sphere);
ball_geo.setMaterial(stone_mat);
rootNode.attachChild(ball_geo);
/** Position the cannon ball */
ball_geo.setLocalTranslation(cam.getLocation());
/** Make the ball physcial with a mass > 0.0f */
ball_phy = new RigidBodyControl(1f);
/** Add physical ball to physics space. */
ball_geo.addControl(ball_phy);
bulletAppState.getPhysicsSpace().add(ball_phy);
/** Accelerate the physcial ball to shoot it. */
ball_phy.setLinearVelocity(cam.getDirection().mult(25));
}
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("Shoot") && !keyPressed) {
// 1. Reset results list.
CollisionResults results = new CollisionResults();
// 2. Aim the ray from cam loc to cam direction.
Ray ray = new Ray(cam.getLocation(), cam.getDirection());
// 3. Collect intersections between Ray and Shootables in results list.
removables.collideWith(ray, results);
// 4. Print the results
System.out.println("----- Collisions? " + results.size() + "-----");
for (int i = 0; i < results.size(); i++) {
// For each hit, we know distance, impact point, name of geometry.
float dist = results.getCollision(i).getDistance();
Vector3f pt = results.getCollision(i).getContactPoint();
String hit = results.getCollision(i).getGeometry().getName();
System.out.println("* Collision #" + i);
System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away.");
}
// 5. Use the results (we mark the hit object)
if (results.size() > 0) {
// The closest collision point is what was truly hit:
Geometry closest = results.getClosestCollision().getGeometry();
// Let's interact - we mark the hit with a red dot.
bulletAppState.getPhysicsSpace().remove(closest.getControl(0));
objectsThatNeedWaking.remove(closest.getControl(0));
removables.detachChild(closest);
bulletAppState.getPhysicsSpace().clearForces();
bulletAppState.getPhysicsSpace().applyGravity();
System.out.println("removing a "+closest.getName());
for(RigidBodyControl wakeMeUp: objectsThatNeedWaking){
wakeMeUp.activate();
}
}
}
}
};
/** A plus sign used as crosshairs to help the player with aiming.*/
protected void initCrossHairs() {
guiNode.detachAllChildren();
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
BitmapText ch = new BitmapText(guiFont, false);
ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
ch.setText("+"); // fake crosshairs :)
ch.setLocalTranslation( // center
settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
guiNode.attachChild(ch);
}
}
Related
Looking this post, I've tried to implement in javaFX, with many difficulties, a Scatter Chart 3D where the grid is my x,y and z axis and the spheres are my points.
How Can I put a legend, axis labels and the range numbers along the axis? I can use only javaFX without external library.
I'm desperate.. I'm trying for days..without results
Please:help me
Thanks.
Code
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class GraphingData extends Application {
private static Random rnd = new Random();
// size of graph
int graphSize = 400;
// variables for mouse interaction
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(150, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(120, Rotate.Y_AXIS);
#Override
public void start(Stage primaryStage) {
// create axis walls
Group grid = createGrid(graphSize);
// initial cube rotation
grid.getTransforms().addAll(rotateX, rotateY);
// add objects to scene
StackPane root = new StackPane();
root.getChildren().add(grid);
root.setStyle( "-fx-border-color: red;");
// create bars
double gridSizeHalf = graphSize / 2;
double size = 30;
//Drawing a Sphere
Sphere sphere = new Sphere();
//Setting the properties of the Sphere
sphere.setRadius(10.0);
sphere.setTranslateX(-50);
sphere.setTranslateY(-50);
//Preparing the phong material of type specular color
PhongMaterial material6 = new PhongMaterial();
//setting the specular color map to the material
material6.setDiffuseColor(Color.GREEN);
sphere.setMaterial(material6);
grid.getChildren().addAll(sphere);
// scene
Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
scene.setOnMousePressed(me -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged(me -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
makeZoomable(root);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* Axis wall
*/
public static class Axis extends Pane {
Rectangle wall;
public Axis(double size) {
// wall
// first the wall, then the lines => overlapping of lines over walls
// works
wall = new Rectangle(size, size);
getChildren().add(wall);
// grid
double zTranslate = 0;
double lineWidth = 1.0;
Color gridColor = Color.RED;
for (int y = 0; y <= size; y += size / 10) {
Line line = new Line(0, 0, size, 0);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateY(y);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
for (int x = 0; x <= size; x += size / 10) {
Line line = new Line(0, 0, 0, size);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateX(x);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
}
public void setFill(Paint paint) {
wall.setFill(paint);
}
}
public void makeZoomable(StackPane control) {
final double MAX_SCALE = 20.0;
final double MIN_SCALE = 0.1;
control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = 1.2;
double scale = control.getScaleX();
if (event.getDeltaY() < 0) {
scale /= delta;
} else {
scale *= delta;
}
scale = clamp(scale, MIN_SCALE, MAX_SCALE);
control.setScaleX(scale);
control.setScaleY(scale);
event.consume();
}
});
}
/**
* Create axis walls
*
* #param size
* #return
*/
private Group createGrid(int size) {
Group cube = new Group();
// size of the cube
Color color = Color.LIGHTGRAY;
List<Axis> cubeFaces = new ArrayList<>();
Axis r;
// back face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.5 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(0.5 * size);
cubeFaces.add(r);
// bottom face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.4 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(0);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// right face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.3 * 1), 1.0));
r.setTranslateX(-1 * size);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// left face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.2 * 1), 1.0));
r.setTranslateX(0);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// top face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-1 * size);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// front face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(-0.5 * size);
// cubeFaces.add( r);
cube.getChildren().addAll(cubeFaces);
return cube;
}
public static double normalizeValue(double value, double min, double max, double newMin, double newMax) {
return (value - min) * (newMax - newMin) / (max - min) + newMin;
}
public static double clamp(double value, double min, double max) {
if (Double.compare(value, min) < 0)
return min;
if (Double.compare(value, max) > 0)
return max;
return value;
}
public static Color randomColor() {
return Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));
}
public static void main(String[] args) {
launch(args);
}
}
Here's a basic idea to create some measures on the axes. It is not production-ready but should give you enough to start with.
private Group createGrid(int size) {
// existing code omitted...
cube.getChildren().addAll(cubeFaces);
double gridSizeHalf = size / 2;
double labelOffset = 30 ;
double labelPos = gridSizeHalf - labelOffset ;
for (double coord = -gridSizeHalf ; coord < gridSizeHalf ; coord+=50) {
Text xLabel = new Text(coord, labelPos, String.format("%.0f", coord));
xLabel.setTranslateZ(labelPos);
xLabel.setScaleX(-1);
Text yLabel = new Text(labelPos, coord, String.format("%.0f", coord));
yLabel.setTranslateZ(labelPos);
yLabel.setScaleX(-1);
Text zLabel = new Text(labelPos, labelPos, String.format("%.0f", coord));
zLabel.setTranslateZ(coord);
cube.getChildren().addAll(xLabel, yLabel, zLabel);
zLabel.setScaleX(-1);
}
return cube;
}
I would just place a legend outside the graph, which would just be a 2D grid pane not rotating...
I know this question is getting old but 2D labels in a JavaFX 3D scene is a topic that comes up a lot and I never see it answered "the right way".
Translating the labels like in James_D's answer will translate into 3D space a 2D label which will look correct until you move the camera. Assuming you want a scatter chart that doesn't move or rotate then this will be fine. Other wise you will need to automatically transform the 2D labels whenever you move your camera. (ie... the mouse handler). You could remove your scatter chart and readd the whole thing to the scene each time but that will be murder on your heap memory and won't be feasible for data sets of any real useful size.
The right way to do it is to use OpenGL or DirectDraw text renders which redraw the labels on each render loop pass but JavaFX 3D doesn't give you access (currently). So the "right way in JavaFX" is to float 2D labels on top of a 3D subscene and then translate them whenever the camera moves. This requires that you transform the 3D coordinate projection of the 3D location you want the label to a 2D screen projection.
To generically manage 2D labels connected to a Point3D in JavaFX 3D you need to do a transform along the following:
Point3D coordinates = node.localToScene(javafx.geometry.Point3D.ZERO);
SubScene oldSubScene = NodeHelper.getSubScene(node);
coordinates = SceneUtils.subSceneToScene(oldSubScene, coordinates);
double x = coordinates.getX();
double y = coordinates.getY();
label.getTransforms().setAll(new Translate(x, y));
Where the node is some actual 3D object already in the 3D subscene. For my applications I simply use a Sphere of an extremely small size it cannot be seen. If you were to follow James_D's example, you could translate the sphere(s) to the same locations that you translated the original axis labels.
The label is a standard JavaFX 2D label that you add to your scene... typically through a StackPane such that the labels are floating on top of the 3D subscene.
Now whenever the camera moves/rotates, this causes this transform to be called which slides the label on the 2D layer. Without direct access to the underlying GL or DD calls this is pretty much the only way to do something like this in JavaFX 3D but it works pretty well.
Here is a video example of it working.
Here is an open source example of implementing a simple version of floating 2D labels. (Warning, I'm the contributing author for the sample, not trying to promote the library.)
I am writing a game based on Jet Set Willy for a personal project. As you will know, the character can move from room to room, collecting items as he goes.
I am using LibGDX and the Tiled Map editor.
I currently load my items based on Object Tiles in my map, which are on a layer called 'Items', as per below:
public void loadItems() {
//////////////////////////////////////////////////////////////////////////
//create all Items
for(MapObject object : map.getLayers().get(4).getObjects().getByType(RectangleMapObject.class)){
Rectangle rect = ((RectangleMapObject) object).getRectangle();
//new Item(screen, object);
items.add(new Item(this, object, (rect.getX() + rect.getWidth() / 2) / Engine.PPM, (rect.getY() + rect.getHeight() / 2) / Engine.PPM));
}
}
The items are stored in an Array on my Playscreen as follows:
public static Array<Item> items;
When the items are collected, I simply remove them from the screen.
To switch rooms I essentially load a new map, fetch that level's Items etc. The problem is, that if I move back to the original room I need to fetch the items again, which draws them all again.
//////////////////////////////////////////////////////////////////////////////
/**
* Load the next Level
*/
public void changeMap(int roomNumber, float x, float y) {
map.dispose();
loadMap(roomNumber);
this.current_level = roomNumber;
renderer.getMap().dispose(); //dispose the old map
renderer.setMap(map); //set the map in your renderer
world = new World(new Vector2(0,-4 ), true);
world.setContactListener(new WorldContactListener());
creator = new B2WorldCreator(this);
loadItems();
//Reposition Player
player = new Player(world, this, x * Engine.TILE_WIDTH, y * Engine.TILE_HEIGHT);
}
My Item class is as follows:
public class Item extends Sprite {
protected World world;
protected PlayScreen screen;
private float stateTime;
protected TiledMap map;
protected MapObject object;
private Animation animation;
private Array<TextureRegion> frames;
private boolean setToDestroy;
private boolean destroyed;
float angle;
public Body b2body;
FixtureDef fdef;
private Texture tex;
private Texture blank_texture;
private int item_number;
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Constructor
* #param screen
* #param object
* #param x
* #param y
*/
public Item(PlayScreen screen, MapObject object, float x, float y){
this.world = screen.getWorld();
this.screen = screen;
this.map = screen.getMap();
//this.item_number = item_number;
setPosition(x, y);
Random rn = new Random();
int max = 2;
int min = 1;
int random = rn.nextInt(5) + 1;
tex = new Texture(Gdx.files.internal("sprites/item" + random + ".png"));
frames = new Array<TextureRegion>();
for(int i = 0; i < 4; i++) {
frames.add(new TextureRegion(tex, i * 16, 0, 16, 16));
}
animation = new Animation(0.1f, frames);
blank_texture = new Texture(Gdx.files.internal("sprites/blank_item.png"));
setBounds(getX(), getY(), 15 / Engine.PPM, 15 / Engine.PPM);
setToDestroy = false;
destroyed = false;
angle = 0;
stateTime = 0;
define_item();
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
*Define the Box2D body for the item
*/
public void define_item() {
BodyDef bdef = new BodyDef();
bdef.position.set(getX(), getY());
bdef.type = BodyDef.BodyType.StaticBody;
b2body = world.createBody(bdef);
fdef = new FixtureDef();
fdef.filter.categoryBits = Engine.ITEM_BIT;
fdef.filter.maskBits = Engine.PLAYER_BIT;
PolygonShape shape = new PolygonShape();
shape.setAsBox(7 / Engine.PPM, 7 / Engine.PPM);
fdef.shape = shape;
b2body.createFixture(fdef).setUserData(this);
b2body.setGravityScale(0);
b2body.setActive(true);
}
public void redefineItem() {
Gdx.app.log("redefineItem", "Item");
Vector2 position = b2body.getPosition();
world.destroyBody(b2body);
BodyDef bdef = new BodyDef();
bdef.position.set(position);
bdef.type = BodyDef.BodyType.StaticBody;
b2body = world.createBody(bdef);
fdef = new FixtureDef();
fdef.filter.categoryBits = Engine.ITEM_BIT;
fdef.filter.maskBits = Engine.PLAYER_BIT;
PolygonShape shape = new PolygonShape();
shape.setAsBox(7 / Engine.PPM, 7 / Engine.PPM);
fdef.shape = shape;
b2body.createFixture(fdef).setUserData(this);
b2body.setGravityScale(0);
b2body.setActive(true);
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Draw Method
* #param batch
*/
#Override
public void draw(Batch batch) {
if(!destroyed) {
super.draw(batch);
}
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Update the Items
* #param dt
*/
public void update(float dt){
setRegion(getFrame(dt));
stateTime += dt;
if(setToDestroy && !destroyed){
world.destroyBody(b2body);
destroyed = true;
setRegion(blank_texture);
stateTime = 0;
}
else if(!destroyed) {
setRegion(animation.getKeyFrame(stateTime, true));
setPosition(b2body.getPosition().x - getWidth() / 2, b2body.getPosition().y - getHeight() / 2);
}
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the Texture
* #param dt
* #return
*/
public TextureRegion getFrame(float dt){
TextureRegion region;
region = animation.getKeyFrame(stateTime, true);
return region;
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Item has been collected
* #param player
*/
public void collected(Player player) {
if(Engine.bPlaySounds) {
Sound sound = Gdx.audio.newSound(Gdx.files.internal("audio/sounds/collect_item.wav"));
sound.play(1.0f);
}
//Change the Category Bit, so that it is no longer collidable
fdef.filter.categoryBits = Engine.COLLECTED_BIT;
this.setToDestroy = true;
Gdx.app.log("Collected Item ", "" + this.item_number + " from room " + screen.getCurrentLevel() );
//Increment the counter on the HUD
screen.incrementItemCounter();
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Set the category Filter
* #param filterBit
*/
public void setCategoryFilter(short filterBit){
Filter filter = new Filter();
filter.categoryBits = filterBit;
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the Tilemap cell
* #return
*/
public TiledMapTileLayer.Cell getCell(){
TiledMapTileLayer layer = (TiledMapTileLayer) map.getLayers().get(0);
return layer.getCell((int)(b2body.getPosition().x * Engine.PPM / 16), (int)(b2body.getPosition().y * Engine.PPM / 16));
}
}
I'd like to store each item I collect in some kind of array, which includes the room number, and the item's X/Y position. When I redraw the items, it will skip any of the items which are in the collected list. Problem is, I'm not sure how to achieve this in Java.
Does anyone have any suggestions on how I might achieve this?
Regards
James
There are many ways you can do this. Here's one suggestion:
Store all your rooms' item lists in a map object and read from the map in loadItems() if appropriate. Also, I would avoid the use of static unless it is really necessary--that can easily lead to sneaky bugs if you're still a bit new to Java, and they aren't usually good object-oriented practice.
private final IntMap<Array<Item>> roomsToItems = new IntMap();
private Array<Item> items;
//...
public void loadItems(int roomNumber) {
items = roomsToItems.get(roomNumber); //get this room's previous list if it exists
if (items == null) { //this room hasn't been loaded yet
items = new Array<>();
//TODO: Load the items into "items"
//store the items list so it can be retrieved instead of loaded next time:
roomsToItems.put(roomNumber, items);
}
}
Then you can safely remove items from items and the list will reflect that the next time you enter the room.
The context behind the this question is that I am part of a small group working to create a small game on Android. The game we have chosen to develop is a top-down 2D shooter where the player controls a spaceship that must defend itself from alien creatures that move towards the player from the top of the gamescreen, similar to space invader, but different in that they spawn in random patterns.
So far, we have been able to get the game running, have the player and enemies to spawn and move in the right direction, as well as the player's weapon which is labelled as "projectile" (although it currently just spawns on its own and not on the player command). The issue we have though is that we are trying to set up a collision event in the code between the projectile and the enemies so that when they meet, the enemy and bullet both disappear.
However, what we have currently written in the code does not seem to make this work, so I was wondering if perhaps anyone else could see where we are going wrong. I've listed the relevant classes of code below. We would greatly appreciate any help anyone can give.
Collision Detector class( Holds the behavoir for collision detection for all entities in the game).
package uk.ac.qub.eeecs.gage.util;
import uk.ac.qub.eeecs.gage.world.GameObject;
/**
* Collision Detector Helper Library
*
* #version 1.0
*/
public class CollisionDetector {
/**
* Type of collision
*/
public enum CollisionType {
None, Top, Bottom, Left, Right, destroy //for the platform game, not yours
};
/**
* Determine if the two specified bounding boxes are in collision
*
* #param one
* First bounding box
* #param two
* Second bounding box
* #return boolean true if the boxes overlap, false otherwise
*/
//is relevant to your game
public static boolean isCollision(BoundingBox one, BoundingBox two) {
return (one.x - one.halfWidth < two.x + two.halfWidth
&& one.x + one.halfWidth > two.x - two.halfWidth
&& one.y - one.halfHeight < two.y + two.halfHeight && one.y
+ one.halfHeight > two.y - two.halfHeight);
//Do we add the code to set the enemies to disappear/destroy here or in the
//individual classes such as the player?
}
/**
* Determine the type of collision between the two bounding boxes.
* CollisionType.None is returned if there are no collisions.
*
* #param one
* First bounding box
* #param two
* Second bounding box
* #return Collision type
*/
AISpaceship.java class (class used for emeny alien entities and their properties).
package uk.ac.qub.eeecs.game.spaceDemo;
import uk.ac.qub.eeecs.gage.ai.SteeringBehaviours;
import uk.ac.qub.eeecs.gage.engine.ElapsedTime;
import uk.ac.qub.eeecs.gage.util.CollisionDetector;
import uk.ac.qub.eeecs.gage.util.Vector2;
import uk.ac.qub.eeecs.gage.util.CollisionDetector.CollisionType;
import uk.ac.qub.eeecs.gage.world.GameObject;
import uk.ac.qub.eeecs.gage.world.Sprite;
/**
* AI controlled spaceship
*
* #version 1.0
*/
public class AISpaceship extends Sprite {
// /////////////////////////////////////////////////////////////////////////
// Properties
// /////////////////////////////////////////////////////////////////////////
/**
* AI control behaviour
*/
/**
* Acceleration with which the spaceship can move along
* the x-axis
*/
private float RUN_ACCELERATION = 150.0f;
public enum ShipBehaviour {
Turret, Seeker
}
private boolean visible;
private ShipBehaviour mShipBehaviour;
/**
* Distance at which the spaceship should avoid other game objects
*/
private float separateThresholdShip = 75.0f;
private float separateThresholdAsteroid = 125.0f;
/**
* Accumulators used to build up the net steering outcome
*/
private Vector2 accAccumulator = new Vector2();
private Vector2 accComponent = new Vector2();
// /////////////////////////////////////////////////////////////////////////
// Constructors
// /////////////////////////////////////////////////////////////////////////
/**
* Create a AI controlled spaceship
*
* #param startX
* x location of the AI spaceship
* #param startY
* y location of the AI spaceship
* #param shipBehaviour
* Steering behaviour to be used by the AI ship
* #param gameScreen
* Gamescreen to which AI belongs
*/
public AISpaceship(float startX, float startY, ShipBehaviour shipBehaviour,
SteeringDemoGameScreen gameScreen) {
super(startX, startY, 50.0f, 50.0f, null, gameScreen);
mShipBehaviour = shipBehaviour;
visible = true;
switch (mShipBehaviour) {
case Turret:
maxAcceleration = 0.0f;
maxVelocity = 0.0f;
mBitmap = gameScreen.getGame().getAssetManager().getBitmap("Turret");
break;
case Seeker:
maxAcceleration = -40.0f;
maxVelocity = 50.0f;
mBitmap = gameScreen.getGame().getAssetManager().getBitmap("enemy sprites 7");
break;
}
}
// /////////////////////////////////////////////////////////////////////////
// Methods
// /////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc)
*
* #see
* uk.ac.qub.eeecs.gage.world.Sprite#update(uk.ac.qub.eeecs.gage.engine.
* ElapsedTime)
*/
#Override
public void update(ElapsedTime elapsedTime) {
switch (mShipBehaviour) {
case Seeker:
//Move down towards the player in a straight line
acceleration.y = RUN_ACCELERATION;
// Seek towards the player
// Try to avoid a collision with the playership
// Try to avoid a collision with the other spaceships
// Try to avoid a collision with the asteroids
SteeringBehaviours.separate(this,
((SteeringDemoGameScreen) mGameScreen).getAsteroids(),
separateThresholdAsteroid, 1.0f, accComponent);
accAccumulator.add(accComponent);
// If we are trying to avoid a collision then combine
// it with the seek behaviour, placing more emphasis on
// avoiding the collision.
if (!accAccumulator.isZero()) {
acceleration.x = 0.3f * acceleration.x + 0.7f
* accAccumulator.x;
acceleration.y = 0.3f * acceleration.y + 0.7f
* accAccumulator.y;
}
// Make sure we point in the direction of travel.
break;
}
// Call the sprite's superclass to apply the determine accelerations
super.update(elapsedTime);
// Check that our new position has not collided by one of the
// defined projectiles. If so, then removing any overlap and
// ensure a valid velocity.
checkForAndResolveCollisions(projectiles);
}
/**
* Check for and then resolve any collision between the AiShip and the
* player projectiles.
*
* #param projectiles
* Array of projectiles to test for collision against
*/
private void checkForAndResolveCollisions(GameObject[] projectiles) {
CollisionType collisionType;
// Consider each platform for a collision
for (GameObject projectile : projectiles) {
collisionType =
CollisionDetector.determineAndResolveCollision(this, Projectile);
switch (collisionType) {
case destroy:
visible = false;
break;
}
}
}
}
Projectile1 (The class used to determine all the game's projectiles and their behavoir).
package uk.ac.qub.eeecs.game.spaceDemo;
import uk.ac.qub.eeecs.gage.ai.SteeringBehaviours;
import uk.ac.qub.eeecs.gage.engine.ElapsedTime;
import uk.ac.qub.eeecs.gage.util.Vector2;
import uk.ac.qub.eeecs.gage.world.Sprite;
/**
* AI controlled spaceship
*
* #version 1.0
*/
public class Projectile1 extends Sprite {
// /////////////////////////////////////////////////////////////////////////
// Properties
// /////////////////////////////////////////////////////////////////////////
/**
* AI control behaviour
*/
/**
* Acceleration with which the projectile can move along
* the x-axis
*/
private float RUN_ACCELERATION = 150.0f;
public enum ShipBehaviour {
bullet
}
private ShipBehaviour mShipBehaviour;
/**
* Distance at which the spaceship should avoid other game objects
*/
/**
* Accumulators used to build up the net steering outcome
*/
private Vector2 accAccumulator = new Vector2();
private Vector2 accComponent = new Vector2();
// /////////////////////////////////////////////////////////////////////////
// Constructors
// /////////////////////////////////////////////////////////////////////////
/**
* Create a AI controlled spaceship
*
* #param startX
* x location of the AI spaceship
* #param startY
* y location of the AI spaceship
* #param shipBehaviour
* Steering behaviour to be used by the AI ship
* #param gameScreen
* Gamescreen to which AI belongs
*/
public Projectile1(float startX, float startY, ShipBehaviour shipBehaviour,
SteeringDemoGameScreen gameScreen) {
super(startX, startY, 50.0f, 50.0f, null, gameScreen);
mShipBehaviour = shipBehaviour;
switch (mShipBehaviour) {
case bullet:
maxAcceleration = 20.0f;
maxVelocity = 30.0f;
mBitmap = gameScreen.getGame().getAssetManager().getBitmap("Spaceship2");
break;
}
}
// /////////////////////////////////////////////////////////////////////////
// Methods
// /////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc)
*
* #see
* uk.ac.qub.eeecs.gage.world.Sprite#update(uk.ac.qub.eeecs.gage.engine.
* ElapsedTime)
*/
#Override
public void update(ElapsedTime elapsedTime) {
switch (mShipBehaviour) {
case bullet:
// Seek towards the player
acceleration.y = RUN_ACCELERATION;
break;
}
// Call the sprite's superclass to apply the determine accelerations
super.update(elapsedTime);
}
}
SterringGameDemo (The main class for the level on which the game play occurs and where the player, enemies and projectiles behave and interact with each other (note-it is called "SteeringGameDemo" because this is a leftover from a template we used to help make the class and has not been renamed yet)).
package uk.ac.qub.eeecs.game.spaceDemo;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import uk.ac.qub.eeecs.gage.Game;
import uk.ac.qub.eeecs.gage.engine.AssetStore;
import uk.ac.qub.eeecs.gage.engine.ElapsedTime;
import uk.ac.qub.eeecs.gage.engine.graphics.IGraphics2D;
import uk.ac.qub.eeecs.gage.util.BoundingBox;
import uk.ac.qub.eeecs.gage.util.CollisionDetector;
import uk.ac.qub.eeecs.gage.world.GameObject;
import uk.ac.qub.eeecs.gage.world.GameScreen;
import uk.ac.qub.eeecs.gage.world.LayerViewport;
import uk.ac.qub.eeecs.gage.world.ScreenViewport;
import android.graphics.Color;
/**
* Simple steering game world
*
* #version 1.0
*/
public class SteeringDemoGameScreen extends GameScreen {
// /////////////////////////////////////////////////////////////////////////
// Properties
// /////////////////////////////////////////////////////////////////////////
/**
* Width and height of the level
*/
private final float LEVEL_WIDTH = 480.0f;
private final float LEVEL_HEIGHT = 280.0f;
/**
* Define viewports for this layer and the associated screen projection
*/
private ScreenViewport mScreenViewport;
private LayerViewport mLayerViewport;
/**
* Define a background object, alongside a player controlled
* space ship and separate lists of asteroids and AI controlled
* space ships.
*/
private GameObject mSpaceBackground;
private PlayerSpaceship mPlayerSpaceship;
private final int NUM_ASTEROIDS = 0;
private List<Asteroid> mAsteroids;
private final int NUM_SEEKERS = 2;
private final int NUM_TURRETS = 1;
private final int NUM_PROJECTILE = 1;
private List<Projectile1> mProjectile1;
private List<AISpaceship> mAISpaceships;
// /////////////////////////////////////////////////////////////////////////
// Constructors
// /////////////////////////////////////////////////////////////////////////
/**
* Create a simple steering game world
*
* #param game
* Game to which this screen belongs
*/
public SteeringDemoGameScreen(Game game) {
super("SteeringDemoGameScreen", game);
// Create the screen viewport
mScreenViewport = new ScreenViewport(0, 0, game.getScreenWidth(),
game.getScreenHeight());
// Create the layer viewport, taking into account the orientation
// and aspect ratio of the screen.
if (mScreenViewport.width > mScreenViewport.height)
mLayerViewport = new LayerViewport(240.0f, 240.0f
* mScreenViewport.height / mScreenViewport.width, 240,
240.0f * mScreenViewport.height / mScreenViewport.width);
else
mLayerViewport = new LayerViewport(240.0f * mScreenViewport.height
/ mScreenViewport.width, 240.0f, 240.0f
* mScreenViewport.height / mScreenViewport.width, 240);
// Load in the assets used by the steering demo
AssetStore assetManager = mGame.getAssetManager();
assetManager.loadAndAddBitmap("Space BG 2", "img/Space BG 2.png");
assetManager.loadAndAddBitmap("Asteroid1", "img/Asteroid1.png");
assetManager.loadAndAddBitmap("Asteroid2", "img/Asteroid2.png");
assetManager.loadAndAddBitmap("enemy sprites 7", "img/enemy sprites 7.png");
assetManager.loadAndAddBitmap("Spaceship2", "img/Spaceship2.png");
assetManager.loadAndAddBitmap("Spaceship3", "img/Spaceship3.png");
assetManager.loadAndAddBitmap("Spaceship3", "img/Spaceship3.png");
assetManager.loadAndAddBitmap("Player sprite", "img/Player sprite.png");
assetManager.loadAndAddBitmap("Turret", "img/Turret.png");
// Create the space background
mSpaceBackground = new GameObject(LEVEL_WIDTH / 2.0f,
LEVEL_HEIGHT / 2.0f, LEVEL_WIDTH, LEVEL_HEIGHT, getGame()
.getAssetManager().getBitmap("Space BG 2"), this);
// Create the player spaceship
mPlayerSpaceship = new PlayerSpaceship(230, 10, this);
// Create a number of randomly positioned asteroids
Random random = new Random();
mAsteroids = new ArrayList<Asteroid>(NUM_ASTEROIDS);
for (int idx = 0; idx < NUM_ASTEROIDS; idx++)
mAsteroids.add(new Asteroid(random.nextFloat() * LEVEL_WIDTH, random.nextFloat() * LEVEL_HEIGHT, this));
// Create a number of randomly positioned AI controlled ships
mAISpaceships = new ArrayList<AISpaceship>(NUM_SEEKERS + NUM_TURRETS);
for (int idx = 0; idx < NUM_SEEKERS; idx++)
mAISpaceships.add(new AISpaceship(random.nextFloat() * LEVEL_WIDTH,
180 + random.nextFloat() * LEVEL_HEIGHT,
AISpaceship.ShipBehaviour.Seeker, this));
for (int idx = 0; idx < NUM_TURRETS; idx++)
mAISpaceships.add(new AISpaceship(random.nextFloat() * LEVEL_WIDTH,
random.nextFloat() * LEVEL_HEIGHT,
AISpaceship.ShipBehaviour.Turret, this));
//Use the above to help spawn the bullets
mProjectile1 = new ArrayList<Projectile1>(NUM_PROJECTILE);
for (int idx = 0; idx < NUM_PROJECTILE; idx++)
mProjectile1.add(new Projectile1(mPlayerSpaceship.position.x, mPlayerSpaceship.position.y,
Projectile1.ShipBehaviour.bullet, this));
}
// /////////////////////////////////////////////////////////////////////////
// Support methods
// /////////////////////////////////////////////////////////////////////////
/**
* Return the player spaceship
*
* #return Player spaceship
*/
public PlayerSpaceship getPlayerSpaceship() {
return mPlayerSpaceship;
}
public List<Projectile1> getProjectile1() {
return mProjectile1;
}
/**
* Return a list of the AI spaceships in the level
*
* #return List of AI controlled spaceships
*/
public List<AISpaceship> getAISpaceships() {
return mAISpaceships;
}
/**
* Return a list of asteroids in the the level
*
* #return List of asteroids in the level
*/
public List<Asteroid> getAsteroids() {
return mAsteroids;
}
// /////////////////////////////////////////////////////////////////////////
// Update and Draw methods
// /////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc) fs
*
* #see
* uk.ac.qub.eeecs.gage.world.GameScreen#update(uk.ac.qub.eeecs.gage.engine
* .ElapsedTime)
*/
#Override
public void update(ElapsedTime elapsedTime) {
// Update the player spaceship
mPlayerSpaceship.update(elapsedTime);
// Ensure the player cannot leave the confines of the world
BoundingBox playerBound = mPlayerSpaceship.getBound();
if (playerBound.getLeft() < 0)
mPlayerSpaceship.position.x -= playerBound.getLeft();
else if (playerBound.getRight() > LEVEL_WIDTH)
mPlayerSpaceship.position.x -= (playerBound.getRight() - LEVEL_WIDTH);
if (playerBound.getBottom() < 0)
mPlayerSpaceship.position.y -= playerBound.getBottom();
else if (playerBound.getTop() > LEVEL_HEIGHT)
mPlayerSpaceship.position.y -= (playerBound.getTop() - LEVEL_HEIGHT);
// Ensure the enemyships cannot leave the confines of the world
//Use the above for player and enemies bullets in this class
//IMPORTANT - THE below code is VITAL for the collision detection and was a
//added by the tutor. It calls the bounding box and collision detector for the
//player and enemy ship. Add this to the above section where the bounding box
// is already called, just below the update method.
BoundingBox playersBound = mPlayerSpaceship.getBound();
for (AISpaceship aiSpaceship : mAISpaceships) {
BoundingBox aiBound = aiSpaceship.getBound();
if(CollisionDetector.isCollision(playersBound, aiBound)) {
}
}
// Focus the layer viewport on the player
// Ensure the viewport cannot leave the confines of the world
if (mLayerViewport.getLeft() < 0)
mLayerViewport.x -= mLayerViewport.getLeft();
else if (mLayerViewport.getRight() > LEVEL_WIDTH)
mLayerViewport.x -= (mLayerViewport.getRight() - LEVEL_WIDTH);
if (mLayerViewport.getBottom() < 0)
mLayerViewport.y -= mLayerViewport.getBottom();
else if (mLayerViewport.getTop() > LEVEL_HEIGHT)
mLayerViewport.y -= (mLayerViewport.getTop() - LEVEL_HEIGHT);
// Update each of the AI controlled spaceships
for (AISpaceship aiSpaceship : mAISpaceships)
aiSpaceship.update(elapsedTime);
// Update each of the asteroids
for (Asteroid asteroid : mAsteroids)
asteroid.update(elapsedTime);
// Update each of the Player Projectile1
for (Projectile1 projectile1 : mProjectile1)
projectile1.update(elapsedTime);
}
/*
* (non-Javadoc)
*
* #see
* uk.ac.qub.eeecs.gage.world.GameScreen#draw(uk.ac.qub.eeecs.gage.engine
* .ElapsedTime, uk.ac.qub.eeecs.gage.engine.graphics.IGraphics2D)
*/
#Override
public void draw(ElapsedTime elapsedTime, IGraphics2D graphics2D) {
// Create the screen to black and define a clip based on the viewport
graphics2D.clear(Color.BLACK);
graphics2D.clipRect(mScreenViewport.toRect());
// Draw the background first of all
mSpaceBackground.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw each of the asteroids
for (Asteroid asteroid : mAsteroids)
asteroid.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw each of the AI controlled spaceships
for (Projectile1 projectile1 : mProjectile1)
projectile1.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw each of the AI controlled spaceships
for (AISpaceship aiSpaceship : mAISpaceships)
aiSpaceship.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw the player
mPlayerSpaceship.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
}
}
Again, I say thank you to anyone who can help.
I've done something similar before.
Try to create a rectangle for every Bullet, Enemy or whatever you've got. The rectangles should have a size thats about the size of the graphics used. Of course the rectangles have to move with the graphics.
You can check if a collision occured simply with
if(rectangleA.intersects(rectangleB)){
doAwesomeStuff();
}
I hope that helps.
I've been playing around with Slick2D for Java and I managed to get it to display maps and have my character sprite move around.
I've tried to implement a camera that follows the player so that the map scrolls. While the map is scrolling, that characters move speed is faster than it should be (possibly due to the camera srolling it as well as it moving with the keys)
I'm stumped on how to solve it though
The camera code is something i found on the slick forums, and modified slightly to draw each layer seperatly, modifying both drawmap methods to add the layer in. I would ask on the forums but they seem dead.
This is the camera code
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package engine;
/**
*
* #author Ceri
*/
import java.awt.geom.Point2D;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.tiled.TiledMap;
public class Camera {
/**
* the map used for our scene
*/
protected TiledMap map;
/**
* the number of tiles in x-direction (width)
*/
protected int numTilesX;
/**
* the number of tiles in y-direction (height)
*/
protected int numTilesY;
/**
* the height of the map in pixel
*/
protected int mapHeight;
/**
* the width of the map in pixel
*/
protected int mapWidth;
/**
* the width of one tile of the map in pixel
*/
protected int tileWidth;
/**
* the height of one tile of the map in pixel
*/
protected int tileHeight;
/**
* the GameContainer, used for getting the size of the GameCanvas
*/
protected GameContainer gc;
/**
* the x-position of our "camera" in pixel
*/
protected float cameraX;
/**
* the y-position of our "camera" in pixel
*/
protected float cameraY;
protected Point2D.Float currentCenterPoint = new Point2D.Float(0, 0);
/**
* Create a new camera
*
* #param gc the GameContainer, used for getting the size of the GameCanvas
* #param map the TiledMap used for the current scene
*/
public Camera(GameContainer gc, TiledMap map) {
this.map = map;
this.numTilesX = map.getWidth();
this.numTilesY = map.getHeight();
this.tileWidth = map.getTileWidth();
this.tileHeight = map.getTileHeight();
this.mapWidth = this.numTilesX * this.tileWidth;
this.mapHeight = this.numTilesY * this.tileHeight;
this.gc = gc;
}
/**
* "locks" the camera on the given coordinates. The camera tries to keep the
* location in it's center.
*
* #param x the real x-coordinate (in pixel) which should be centered on the
* screen
* #param y the real y-coordinate (in pixel) which should be centered on the
* screen
* #return
*/
public Point2D.Float centerOn(float x, float y) {
//try to set the given position as center of the camera by default
cameraX = x - gc.getWidth() / 2;
cameraY = y - gc.getHeight() / 2;
//if the camera is at the right or left edge lock it to prevent a black bar
if (cameraX < 0) {
cameraX = 0;
}
if (cameraX + gc.getWidth() > mapWidth) {
cameraX = mapWidth - gc.getWidth();
}
//if the camera is at the top or bottom edge lock it to prevent a black bar
if (cameraY < 0) {
cameraY = 0;
}
if (cameraY + gc.getHeight() > mapHeight) {
cameraY = mapHeight - gc.getHeight();
}
currentCenterPoint.setLocation(cameraX, cameraY);
return currentCenterPoint;
}
/**
* "locks" the camera on the center of the given Rectangle. The camera tries
* to keep the location in it's center.
*
* #param x the x-coordinate (in pixel) of the top-left corner of the
* rectangle
* #param y the y-coordinate (in pixel) of the top-left corner of the
* rectangle
* #param height the height (in pixel) of the rectangle
* #param width the width (in pixel) of the rectangle
*/
public void centerOn(float x, float y, float height, float width) {
this.centerOn(x + width / 2, y + height / 2);
}
/**
* "locks the camera on the center of the given Shape. The camera tries to
* keep the location in it's center.
*
* #param shape the Shape which should be centered on the screen
*/
public void centerOn(Shape shape) {
this.centerOn(shape.getCenterX(), shape.getCenterY());
}
/**
* draws the part of the map which is currently focussed by the camera on
* the screen
*/
public void drawMap(int layer) {
this.drawMap(0, 0, layer);
}
/**
* draws the part of the map which is currently focussed by the camera on
* the screen.<br>
* You need to draw something over the offset, to prevent the edge of the
* map to be displayed below it<br>
* Has to be called before Camera.translateGraphics() !
*
* #param offsetX the x-coordinate (in pixel) where the camera should start
* drawing the map at
* #param offsetY the y-coordinate (in pixel) where the camera should start
* drawing the map at
*/
public void drawMap(int offsetX, int offsetY, int layer) {
//calculate the offset to the next tile (needed by TiledMap.render())
int tileOffsetX = (int) -(cameraX % tileWidth);
int tileOffsetY = (int) -(cameraY % tileHeight);
//calculate the index of the leftmost tile that is being displayed
int tileIndexX = (int) (cameraX / tileWidth);
int tileIndexY = (int) (cameraY / tileHeight);
//finally draw the section of the map on the screen
map.render(
tileOffsetX + offsetX,
tileOffsetY + offsetY,
tileIndexX,
tileIndexY,
(gc.getWidth() - tileOffsetX) / tileWidth + 1,
(gc.getHeight() - tileOffsetY) / tileHeight + 1, layer, false);
}
/**
* Translates the Graphics-context to the coordinates of the map - now
* everything can be drawn with it's NATURAL coordinates.
*/
public void translateGraphics() {
gc.getGraphics().translate(-cameraX, -cameraY);
}
/**
* Reverses the Graphics-translation of Camera.translatesGraphics(). Call
* this before drawing HUD-elements or the like
*/
public void untranslateGraphics() {
gc.getGraphics().translate(cameraX, cameraY);
}
}
and this is how its being called
In the engine class
public void render(GameContainer gc, Graphics g) throws SlickException {
camera = new Camera(gc, world.map);
camera.centerOn(player.getX(), player.getY());
camera.drawMap(0);
camera.drawMap(1);
player.draw();
camera.drawMap(2);
}
This is how the player class is
public Player(MapClass m) throws SlickException {
map = m;
Image[] movementUp = {new Image("Images/Player/u1.png"), new Image("Images/Player/u2.png"), new Image("Images/Player/u3.png"), new Image("Images/Player/u4.png")};
Image[] movementDown = {new Image("Images/Player/d1.png"), new Image("Images/Player/d2.png"), new Image("Images/Player/d3.png"), new Image("Images/Player/d4.png")};
Image[] movementLeft = {new Image("Images/Player/l1.png"), new Image("Images/Player/l2.png"), new Image("Images/Player/l3.png"), new Image("Images/Player/l4.png")};
Image[] movementRight = {new Image("Images/Player/r1.png"), new Image("Images/Player/r2.png"), new Image("Images/Player/r3.png"), new Image("Images/Player/r4.png")};
int[] duration = {100, 100, 100, 100};
up = new Animation(movementUp, duration, false);
down = new Animation(movementDown, duration, false);
left = new Animation(movementLeft, duration, false);
right = new Animation(movementRight, duration, false);
// Original orientation of the sprite. It will look right.
sprite = right;
}
public void update(GameContainer container, int delta) throws SlickException {
Input input = container.getInput();
if (input.isKeyDown(Input.KEY_UP)) {
sprite = up;
sprite.update(delta);
// The lower the delta the slowest the sprite will animate.
if (!map.isBlocked(x, y - delta * 0.1f))
y -= delta * 0.1f;
} else if (input.isKeyDown(Input.KEY_DOWN)) {
sprite = down;
sprite.update(delta);
if (!map.isBlocked(x, y + 16 + delta * 0.1f))
y += delta * 0.1f;
} else if (input.isKeyDown(Input.KEY_LEFT)) {
sprite = left;
sprite.update(delta);
if (!map.isBlocked(x - delta * 0.1f, y))
x -= delta * 0.1f;
} else if (input.isKeyDown(Input.KEY_RIGHT)) {
sprite = right;
sprite.update(delta);
if (!map.isBlocked(x + 16 + delta * 0.1f, y))
x += delta * 0.1f;
}
}
public void draw() {
sprite.draw(x, y);
}
Fixed it. Moved the map draw out of the camera class into the map class. used the camera x/y created in the camera class in the map and player class
I'm trying to initialize the breakout game so that the game starts with the paddle added to the canvas at a specified location then have the same paddle move with the mouse.
So, 1. create paddle and add on canvas
2. move paddle as it tracks the mouse's position.
PROBLEM: Paddle added to canvas stays where it is but another paddle NOT ADDED to canvas moves according to event listener. One paddle stays stationary and another tracks the mouse.
I have moved the add(paddle) statement to the mouse
ENV: Mac OSX 10.8.4 , JVM 1.6 (installed with OSX)
Is there some kind of setting in java that needs to be set to refresh the paddle upon mouse event? or is it an environmental issue?
/*
* File: Breakout.java
* -------------------
* Name:
* Section Leader:
*
* This file will eventually implement the game of Breakout.
*/
import acm.graphics.*;
import acm.program.*;
import acm.util.*;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Breakout extends GraphicsProgram {
/** Width and height of application window in pixels */
public static final int APPLICATION_WIDTH = 400;
public static final int APPLICATION_HEIGHT = 600;
/** Dimensions of game board
* Should not be used directly (use getWidth()/getHeight() instead).
* * */
private static final int WIDTH = APPLICATION_WIDTH;
private static final int HEIGHT = APPLICATION_HEIGHT;
/** Dimensions of the paddle */
private static final int PADDLE_WIDTH = 60;
private static final int PADDLE_HEIGHT = 10;
/** Offset of the paddle up from the bottom */
private static final int PADDLE_Y_OFFSET = 30;
/** Number of bricks per row */
private static final int NBRICKS_PER_ROW = 10;
/** Number of rows of bricks */
private static final int NBRICK_ROWS = 10;
/** Separation between bricks */
private static final int BRICK_SEP = 4;
/** Width of a brick */
private static final int BRICK_WIDTH =
(WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
/** Height of a brick */
private static final int BRICK_HEIGHT = 8;
/** Radius of the ball in pixels */
private static final int BALL_RADIUS = 10;
/** Offset of the top brick row from the top */
private static final int BRICK_Y_OFFSET = 70;
/** Number of turns */
private static final int NTURNS = 3;
/** ADDED KNM Private Instance Variables */
private GRect paddle;
/* Method: run() */
/** Runs the Breakout program. */
public void run() {
/* You fill this in, along with any subsidiary methods */
init();
}
public void init(){
addMouseListeners();
setSize(APPLICATION_WIDTH,APPLICATION_HEIGHT);
prepTiles();
prepPaddle();
}
private void prepTiles(){
for (int i = 1; i <= NBRICK_ROWS; i++){
int x_pos_start = 0;//(CANVAS_MIDDLE - BRICK_WIDTH/2) - (NBRICKS_PER_ROW/2 - 2)*BRICK_WIDTH;
int y_pos = BRICK_Y_OFFSET + (i-1)*(BRICK_HEIGHT+BRICK_SEP);
Color BRICK_COLOR = getBRICK_COLOR(i);
//case for color fills
for(int y = 1; y <= NBRICKS_PER_ROW; y++ ){
//adjust to beginning of row then add bricks
int x_pos = x_pos_start + ((y-1)*(BRICK_WIDTH+BRICK_SEP));
G3DRect grect = new G3DRect(x_pos, y_pos, BRICK_WIDTH, BRICK_HEIGHT);
grect.setFilled(true);
grect.setColor(BRICK_COLOR);
add(grect);
}
}
}
public void prepPaddle(){
double x_pos = APPLICATION_WIDTH/2 - PADDLE_WIDTH/2;
double y_pos = APPLICATION_HEIGHT - PADDLE_HEIGHT - PADDLE_Y_OFFSET;
paddle = new GRect(x_pos, y_pos, PADDLE_WIDTH, PADDLE_HEIGHT);
paddle.setFilled(true);
paddle.setColor(Color.DARK_GRAY);
add(paddle); // want to keep this.
}
public void mouseMoved(MouseEvent e){
// add(paddle); // I tried moveing the statement here but, that doesn't create the paddle until the mouse hovers above the application canvas.
paddle.setLocation(e.getX() - PADDLE_WIDTH/2, paddle.getY());
if (paddle.getX() <= 0) paddle.setLocation(0, paddle.getY());
if (paddle.getX() + PADDLE_WIDTH >= getWidth()) paddle.setLocation(getWidth() - PADDLE_WIDTH, paddle.getY());
}
private Color getBRICK_COLOR(int i_row){
switch (i_row){
case 1 :
case 2 : return Color.RED;
case 3 :
case 4 : return Color.ORANGE;
case 5 :
case 6 : return Color.yellow;
case 7 :
case 8 : return Color.green;
case 9 :
case 10: return Color.cyan;
default: return Color.black;
}
}
}
You probably just need to call repaint() after moving the paddle.
turns out i was calling init() procedure for the wrong reasons. I renamed the called caused the problem. i changed the function to something else and it all worked out fine. trying to figure out what happened ther