How can I shoot in the direction that a cross-hair is pointing at?
Using the JMonkey engine, I am creating a game where I need a ship to shoot other ships.
So, I created cross-hairs that can move on the screen (up, down, left, right) according to user input, so the user can aim on a certain place.
Now I need I to shoot a cannon from my ship, in the direction that the cross-hair is standing at.
How can I shoot at the place that the cross-hair is pointing at?
You can get camera direction with:
directionXYZ=cam.getDirection(); //Vector3f form
and can get position from:
positionXYZ=cam.getLocation(); //Vector3f
You can have a ray casting:
Ray ray = new Ray(directionXYZ, positionXYZ);
then can collect data for collisions:
shootables.collideWith(ray, results)
where shootable is a "Node".
Finally, check for what you want:
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.");
}
Taken from jmonkey wiki
My reading of this question is that the aim is not to shoot where the camera is facing but where a cursor (not in the centre of the screen) is pointing.
This can be achieved using the cam.getWorldCoordinates(screenPosition, zDepth); command, this returns the 3D point in space that would end up at the point screenPosition on the screen. So if we create a point at zDepth of zero and a point at zDepth of one we can create a ray coming from the cursor position travelling outwards so anything that the curser is "over" is selected. screenPosition is in pixels from the bottom left of the window
An example program that uses this technique is as follows and is based upon the second part of hello picking.
In my example the cursor is moved using the keyboard (H,J,K,U) but a mouse click could also be used (but I'm using the mouse for looking around)
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
public class Main extends SimpleApplication {
public static KeyBindings keyBindings;
public Vector2f cursorPosition=new Vector2f(100,100);
public Node shootables=new Node();
public Node crossHairNode=new Node();
public static void main(String[] args) {
Main app = new Main();
app.start();
}
#Override
public void simpleInitApp() {
//bind keys to move cursor
keyBindings=new KeyBindings(inputManager); //for managing keystrokes
keyBindings.registerKeyBinding(KeyInput.KEY_SPACE, "fire");
keyBindings.registerKeyBinding(KeyInput.KEY_U, "up");
keyBindings.registerKeyBinding(KeyInput.KEY_J, "down");
keyBindings.registerKeyBinding(KeyInput.KEY_H, "left");
keyBindings.registerKeyBinding(KeyInput.KEY_K, "right");
initGui();
Box b = new Box(Vector3f.ZERO, 2, 2, 2);
Geometry geom = new Geometry("BigBox", b);
Box b2 = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry geom2 = new Geometry("SmallBox", b2);
geom2.setLocalTranslation(3, 0, 3);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geom.setMaterial(mat);
geom2.setMaterial(mat);
rootNode.attachChild(shootables);
shootables.attachChild(geom);
shootables.attachChild(geom2);
}
#Override
public void simpleUpdate(float tpf) {
updateCrossHairs();
if (keyBindings.getBinding("fire").hasBecomeTrueSinceLastAccess()){
CollisionResults results = new CollisionResults();
Vector3f cursor3dLocation = cam.getWorldCoordinates(
new Vector2f(cursorPosition.x, cursorPosition.y), 0f).clone();
Vector3f dir = cam.getWorldCoordinates(
new Vector2f(cursorPosition.x, cursorPosition.y), 1f).subtractLocal(cursor3dLocation).normalizeLocal();
Ray ray = new Ray(cursor3dLocation, dir);
shootables.collideWith(ray, results);
if (results.size()>0){
resultsText.setText("Hit: " + results.getClosestCollision().getGeometry().getName());
resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);
}else{
resultsText.setText("Missed");
resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);
}
}
}
private void updateCrossHairs(){
if (keyBindings.getBinding("up").getValue()==true){
cursorPosition.y+=1;
}
if (keyBindings.getBinding("down").getValue()==true){
cursorPosition.y+=-1;
}
if (keyBindings.getBinding("left").getValue()==true){
cursorPosition.x+=-1;
}
if (keyBindings.getBinding("right").getValue()==true){
cursorPosition.x+=+1;
}
crossHairNode.setLocalTranslation(cursorPosition.x - crossHair.getLineWidth()/2,cursorPosition.y + crossHair.getLineHeight()/2, 0);
}
BitmapText crossHair;
BitmapText instructions;
BitmapText resultsText;
private void initGui() {
setDisplayStatView(false);
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
crossHair = new BitmapText(guiFont, false);
crossHair.setSize(guiFont.getCharSet().getRenderedSize() * 2);
crossHair.setText("+"); // crosshairs
crossHairNode.setLocalTranslation(cursorPosition.x - crossHair.getLineWidth()/2,cursorPosition.y + crossHair.getLineHeight()/2, 0);
guiNode.attachChild(crossHairNode);
crossHairNode.attachChild(crossHair);
instructions= new BitmapText(guiFont, false);
instructions.setSize(guiFont.getCharSet().getRenderedSize());
instructions.setText("Move cross hairs with U,H,J,K keys, fire with space \n (WSAD moves camera position and look with mouse)");
instructions.setLocalTranslation(0, settings.getHeight(), 0);
guiNode.attachChild(instructions);
resultsText= new BitmapText(guiFont, false);
resultsText.setSize(guiFont.getCharSet().getRenderedSize());
resultsText.setText("Press Space to fire");
resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);
guiNode.attachChild(resultsText);
}
#Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
}
Keybindings, used only to control cursor movement:
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import java.util.ArrayList;
import java.util.Locale;
public class KeyBindings implements ActionListener{
private InputManager inputManager;
private ArrayList<String> bindingString=new ArrayList<String>(100);
private ArrayList<KeyBinding> binding=new ArrayList<KeyBinding>(100);
public KeyBindings(InputManager inputManager){
this.inputManager=inputManager;
}
public void registerKeyBinding(int key,String bindingName){
bindingName=preprocess(bindingName);
inputManager.addMapping( bindingName, new KeyTrigger(key));
inputManager.addListener(this, bindingName);
binding.add(new KeyBinding());
bindingString.add(bindingName);
}
public void registerMouseBinding(int button,String bindingName){
bindingName=preprocess(bindingName);
inputManager.addMapping( bindingName, new MouseButtonTrigger(button));
inputManager.addListener(this, bindingName);
binding.add(new KeyBinding());
bindingString.add(bindingName);
}
public KeyBinding getBinding(String bindingName){
//get which binding we're after
bindingName=preprocess(bindingName);
int index=bindingString.indexOf(bindingName);
if (index!=-1){
return binding.get(index);
}else{
return null;
}
}
public void onAction(String bindingName, boolean isPressed, float tpf) {
bindingName=preprocess(bindingName);
//get which binding we're after
int index=bindingString.indexOf(bindingName);
if (index!=-1){
binding.get(index).setValue(isPressed);
}
}
private String preprocess(String string){
return string.toUpperCase();
}
}
public class KeyBinding {
private boolean value;
private boolean changedSinceLastAccess; //to avoid multiclicks etc
private boolean valueTrueSinceLastAccess;
public void setValue(boolean value){
this.value=value;
changedSinceLastAccess=true;
if (value==true){
valueTrueSinceLastAccess=true;
}
}
public boolean hasChangedSinceLastAccess(){
return changedSinceLastAccess;
}
public boolean hasBecomeTrueSinceLastAccess(){
//this collects any trues since the last access, is good for things like mouse clicks,
//which are instantaneous and you want then recorded on the next tick
if (valueTrueSinceLastAccess==true){
valueTrueSinceLastAccess=false;
return true;
}else{
return false;
}
}
public boolean getValue(){
changedSinceLastAccess=false;
valueTrueSinceLastAccess=false;
return value;
}
public boolean getValue_withoutNotifying(){
return value;
}
}
Related
So I'm trying to create a game and it's my first time. My game is a 2D side scroller and is about the player in space avoiding the incoming meteors. I have successfully managed to get the meteors spawning randomly on the x and y axis off screen and re position once it has gone pass the screen.
But the problem I face now is sometimes the spawn of the meteors will clump together which I don't want. How do I get the meteors to spawn at a certain distance from each other so they don't clump together. I couldn't find any good tutorials or if anyone can point me to the right direction. Below are my codes so far.
Meteor Class
public class Meteors {
private Texture bigMeteor;
private Vector2 posBigMeteor;
private Random yrand;
//Constructor
public Meteors(float x){
bigMeteor = new Texture("meteor.png");
yrand = new Random();
//Spawn location of meteor
posBigMeteor = new Vector2(x, yrand.nextInt(AstroDemo.HEIGHT/2 - bigMeteor.getHeight()));
}
public Texture getBigMeteor() {
return bigMeteor;
}
public Vector2 getPosBigMeteor() {
return posBigMeteor;
}
//Reposition the meteors
public void reposition(float x){
posBigMeteor.set(x, yrand.nextInt(AstroDemo.HEIGHT/2 - bigMeteor.getHeight()));
}
}
PlayState Class
public class PlayState extends State {
//Total meteor count on screen
private static final int METEOR_COUNT = 8;
private Naught naught;
private Texture bg;
private Random xrand;
private Array <Meteors> meteors;
public PlayState(GameStateManager gsm) {
super(gsm);
//Starting co-ordinates of main character (Naught)
naught = new Naught(50, 100);
//Setting viewport of the camera
cam.setToOrtho(false, AstroDemo.WIDTH/2, AstroDemo.HEIGHT/2);
bg = new Texture("bg.png");
xrand = new Random();
meteors = new Array <Meteors>();
//Spawn meteors randomly off screen
for (int i = 1; i <= METEOR_COUNT; i++){
meteors.add(new Meteors(AstroDemo.WIDTH/2 + (xrand.nextInt(300))));
}
}
#Override
protected void handleInput() {
//If screen/mouse is held
if(Gdx.input.isTouched()){
//Main Character jumps/flys
naught.jump();
}
}
#Override
public void update(float dt) {
handleInput();
naught.update(dt);
//If meteors are left side of the screen, re-position to the right side of the screen
for(Meteors meteor : meteors){
if (cam.position.x - (cam.viewportWidth/2) > meteor.getPosBigMeteor().x + meteor.getBigMeteor().getWidth()){
meteor.reposition(meteor.getPosBigMeteor().x + (AstroDemo.WIDTH/2 + 20 + (xrand.nextInt(300))));
}
}
cam.position.x = naught.getPosition().x + 80;
cam.update();
}
#Override
public void render(SpriteBatch sb) {
//Adjust the spritebatch for co-ordinate system in relation to camera
sb.setProjectionMatrix(cam.combined);
sb.begin();
//Draw background where the camera is
sb.draw(bg, cam.position.x - (cam.viewportWidth/2), 0);
sb.draw(naught.getTexture(), naught.getPosition().x, naught.getPosition().y);
for (Meteors meteor : meteors) {
sb.draw(meteor.getBigMeteor(), meteor.getPosBigMeteor().x, meteor.getPosBigMeteor().y);
}
sb.end();
}
#Override
public void dispose() {
}
}
create a array of predefined value for your y position and then get value randomly from that array.
or
Divide height into sub portion then get random value from that portion so that each random value not collide with other value.
Hello StackOverflow community!
I have been working on a game project and have recently run into an error in my Spritesheet class.
Problem: The game is developed in LibGDX which has a built-in feature that lets you split apart an image into a 2D array with the first dimension being the row and the second being the column.
E.g: spriteSheet[0][1] would give you the second column of the first row.
So I came up with a little method to generate animations using the row but when I run the game, the animation doesn't seem to work as the player remains static the whole time!
There are 4 classes involved in this process:
The Player class:
package com.darkbyte.games.tfa.game.entity.entities;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.SpriteSheet;
import com.darkbyte.games.tfa.render.RenderManager;
public class Player extends Entity {
//The constructor for the player class
public Player(String name, SpriteSheet spriteSheet) {
super(name, spriteSheet);
direction = Direction.DOWN;
}
//A flag to see if the player is moving
private boolean isMoving;
//The player's walking animations
private Animation[] walkAnimations = {
spriteSheet.getAnimation(4, 1/16f),
spriteSheet.getAnimation(5, 1/16f),
spriteSheet.getAnimation(6, 1/16f),
spriteSheet.getAnimation(7, 1/16f)
};
//The player's static frames
private TextureRegion[] staticFrames = {
spriteSheet.getTexture(4, 0),
spriteSheet.getTexture(5, 0),
spriteSheet.getTexture(6, 0),
spriteSheet.getTexture(7, 0)
};
//The render code for the player
#Override
public void render() {
//Gets the player's direction, if the player's moving, it sets the current frame to the frame that would be played at the current moment based on the state time
//If the player isn't moving, it sets the current frame to the static frame associated to the direction
switch(direction) {
case UP:
if(isMoving)
currentFrame = walkAnimations[0].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[0];
break;
case LEFT:
if(isMoving)
currentFrame = walkAnimations[1].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[1];
break;
case DOWN:
if(isMoving)
currentFrame = walkAnimations[2].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[2];
break;
case RIGHT:
if(isMoving)
currentFrame = walkAnimations[3].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[3];
break;
}
}
//The tick code for the player
#Override
public void tick() {
if(Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP)) {
direction = Direction.UP;
y += 2;
} else if(Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT)) {
direction = Direction.LEFT;
x -= 2;
} else if(Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN)) {
direction = Direction.DOWN;
y -= 2;
} else if(Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT)) {
direction = Direction.RIGHT;
x += 2;
} else {
}
}
//Returns if the player is moving
public boolean isMoving() {
return isMoving;
}
}
The RenderManager class:
package com.darkbyte.games.tfa.render;
import javax.swing.JOptionPane;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.darkbyte.ammarlib.files.DataWriter;
import com.darkbyte.games.tfa.Core;
import com.darkbyte.games.tfa.music.MusicManager;
import com.darkbyte.games.tfa.screens.ScreenManager;
import com.darkbyte.games.tfa.screens.UnidentifiedScreenStateException;
public class RenderManager {
//timeSinceLastFramesPerSecondCheck is used to store the clock time since the last frames per second check was done
private static long timeSinceLastFramesPerSecondCheck;
//framesPassed is used to store the number of frames passed in a second
private static int framesPassed, framesPerSecond;
//The variable that stores the current state time of the render, used for animations
private static float stateTime;
//Initialisation for everything render related
public static void init() {
Camera.init();
Batch.init();
Button.init();
}
//Disposal for everything render related
public static void dispose() {
Batch.dispose();
Button.dispose();
}
//The main render method
public static void render() {
//Gets the current clock time in milliseconds
long now = System.currentTimeMillis();
//Increments the frames passed
framesPassed++;
//Updates the state time variable
stateTime += Gdx.graphics.getDeltaTime();
//Checks if 1 second has passed since the last frames per second check
if(now >= timeSinceLastFramesPerSecondCheck + 1000) {
//The frames per second is set to the frames passed that second
framesPerSecond = framesPassed;
//The frames passed is reset
framesPassed = 0;
//Sets the time since the last frames per second check to now
timeSinceLastFramesPerSecondCheck = now;
}
//Clears the screen from the previous render call
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//Renders the correct screen from the screen manager and plays the music for that screen
try {
ScreenManager.renderScreen();
MusicManager.playMusic();
} catch (UnidentifiedScreenStateException e) { //If the screen state runs into an error
//Prints the error log to a time-stamped file
DataWriter writer = new DataWriter(); //Creates a new data writer to write to the file
writer.openFile(Core.getTime(true) + ".txt"); //Opens a the file
writer.write("Tetros: First Age - An error has occurred!\nType: UnidentifiedScreenStateException in RenderManager!"); //Writes some basic data about the error
writer.write(e.getMessage()); //Prints a detailed error log from the exception
writer.closeFile(); //Closes the file, housekeeping reasons
e.printStackTrace(); //Prints the error log to the console
//Shows the client an error message in a dialog box
JOptionPane.showMessageDialog(null, "An unexpected error occured: UnidentifiedScreenStateException in RenderManager!", "Error", JOptionPane.ERROR_MESSAGE);
//Closes the game
System.exit(0);
}
}
//Returns the ticks per second rate
public static int getFPS() {
return framesPerSecond;
}
//Returns the frames passed
public static int getFramesPassed() {
return framesPassed;
}
//Returns the current state time
public static float getStateTime() {
return stateTime;
}
}
The SpriteSheet class:
package com.darkbyte.games.tfa.game.entity;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.*;
public class SpriteSheet {
//All the frames from the entity
private TextureRegion[][] textureSheet;
//Gets the sprite sheet for the entity and splits it into frames
public SpriteSheet(Texture spriteSheet, int tileWidth, int tileHeight) {
//Splits the textures into a two dimensional array
/*
* Dimension 1: Row
* Dimension 2: Column
* Example:
* ********
* ****0***
* ********
* ********
* 0 = textureSheet[1][4] to access the second row and the fifth column
* Some of the columns may be blank, check the sprite sheet to reference
*/
textureSheet = TextureRegion.split(spriteSheet, tileWidth, tileHeight);
}
//Disposes of the textures by looping through the rows then the individual columns and disposing of all the textures
public void dispose() {
for(TextureRegion[] textureRow : textureSheet) for(TextureRegion texture : textureRow) texture.getTexture().dispose();
}
//Returns the texture at a certain row or column (starting at 1,1 being textureSheet[0][0])
public TextureRegion getTexture(int row, int column) {
return textureSheet[row][column];
}
//Returns an animation using pieces of the sprite sheet
public Animation getAnimation(int row, float animationFPS) {
//Returns the animation generated using the frames from the row specified and the FPS from the parameters
return new Animation(animationFPS, textureSheet[row]);
}
}
The TestRoom class:
package com.darkbyte.games.tfa.game.world.room.rooms;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.EntityManager;
import com.darkbyte.games.tfa.game.world.room.Room;
import com.darkbyte.games.tfa.render.Batch;
import com.darkbyte.games.tfa.render.Camera;
public class TestRoom extends Room {
//Initialises the room's tiled map and map renderer
#Override
public void init() {
this.tileMap = Room.generateMap("assets/world/rooms/test.tmx");
this.mapRenderer = new OrthogonalTiledMapRenderer(this.tileMap);
entitiesInRoom.add(EntityManager.getPlayer(0, 0, 0));
}
//Disposes of the room
#Override
public void dispose() {
this.tileMap.dispose();
}
//Runs the render code for the room
#Override
public void render() {
//Sets the map renderer's viewpoint to the viewpoint of the camera
mapRenderer.setView(Camera.getCamera());
//Renders the map
mapRenderer.render();
//Begins the drawing batch
Batch.getGameBatch().begin();
//Loops through the entities in the room and renders them
for(Entity entityToRender : entitiesInRoom) {
entityToRender.render();
Batch.getGameBatch().draw(entityToRender.getCurrentFrame(), entityToRender.getX(), entityToRender.getY());
}
//Ends the drawing batch
Batch.getGameBatch().end();
}
//Runs the tick code for the room
#Override
public void tick() {
//Loops through the entities in the room and runs their tick code
for(Entity entityToRender : entitiesInRoom) entityToRender.tick();
}
}
Instead of building all the animation mechanism yourself, you should use the Libgdx's Animation, which is much easier to use. Read the wiki.
I'm trying to drag nodes about and drop them onto each other. This is a simple class that I expected would react to drag gestures but it doesn't
public class CircleDrag extends Circle
{
double x, y;
String name;
int count = 0;
public CircleDrag(double centerX, double centerY, String name)
{
super(centerX, centerY, 10);
this.name = name;
setOnDragDetected(e ->
{
startFullDrag();
startDragAndDrop(TransferMode.ANY); // tried with and without this line.
logIt(e);
});
setOnDragEntered(e ->
{
logIt(e);
});
setOnDragDone(e ->
{
logIt(e);
});
setOnDragOver(e ->
{
logIt(e);
});
setOnMousePressed(e ->
{
logIt(e);
setMouseTransparent(true);
x = getLayoutX() - e.getSceneX();
y = getLayoutY() - e.getSceneY();
});
setOnMouseReleased(e ->
{
logIt(e);
setMouseTransparent(false);
});
setOnMouseDragged(e ->
{
logIt(e);
setLayoutX(e.getSceneX() + x);
setLayoutY(e.getSceneY() + y);
});
}
private void logIt(Event e)
{
System.out.printf("%05d %s: %s\n", count++, name, e.getEventType().getName());
}
}
I was expecting to add a bunch of CircleDrags to a pane and when dragging one onto another the other would fire an onDrag* event. But it doesn't.
What is it I don't understand about this gesture?
Thanks
Ollie.
Here's how you could do it in general:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class PhysicsTest extends Application {
public static List<Circle> circles = new ArrayList<Circle>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Circle circle1 = new Circle( 50);
circle1.setStroke(Color.GREEN);
circle1.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.3));
circle1.relocate(100, 100);
Circle circle2 = new Circle( 50);
circle2.setStroke(Color.BLUE);
circle2.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
circle2.relocate(200, 200);
MouseGestures mg = new MouseGestures();
mg.makeDraggable( circle1);
mg.makeDraggable( circle2);
circles.add( circle1);
circles.add( circle2);
root.getChildren().addAll(circle1, circle2);
primaryStage.setScene(new Scene(root, 1600, 900));
primaryStage.show();
}
public static class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable( Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
Circle p = ((Circle) (t.getSource()));
orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Circle p = ((Circle) (t.getSource()));
p.setCenterX(newTranslateX);
p.setCenterY(newTranslateY);
for( Circle c: circles) {
if( c == p)
continue;
if( c.getBoundsInParent().intersects(p.getBoundsInParent())) {
System.out.println( "Overlapping!");
}
}
}
};
}
}
Please note that this solution uses the bounds in the parent, ie in the end a rectangle is used for overlap check. If you want to use eg a circle check, you could use the radius and check the distance between the circles. Depends on your requirement.
Challenges with your current solution
You need to put some content in the dragboard
If you don't put anything in the dragboard when the drag is initially detected, there is nothing to drag, so subsequent drag related events such as dragEntered, dragDone and dragOver will never be fired.
Conflating both "dragging the node using a mouse" with "drag and drop content processing" is hard
I couldn't get it to work exactly as you have it with the drag handled by mouse drag events as well as having a drag and drop operation in effect because as soon as the drag and drop operation took effect, the node stopped receiving mouse drag events.
Sample Solution
As a result of the above challenges, the approach I took was:
Put something in the dragboard when a drag is detected.
Remove the mouse event handlers and only use drag event handlers.
Simulate dragging the node around by taking a snapshot of the node to an image, then hiding the node and making use of the DragView with the node image.
When the drag process completes, detect the current location of the mouse cursor then relocate the node to that location.
Unfortunately, JavaFX drag events are unlike mouse events. The drag events don't seem to include full location information (e.g. x,y or sceneX,sceneY). This means you need a way to determine this information independent of the event. I don't know of an API in JavaFX to detect the current location of the mouse cursor, so I had to resort to the awt MouseInfo class to determine the current mouse location.
In the process, I lost a little bit of the accuracy in initial and final node location calculation. For small circles, that doesn't not seem to matter. For other apps, you could probably modify my logic slightly to make the drag and drop transitions 100% accurate and smooth.
I used Java 8 for the sample solution (DragView is not available in Java 7). CircleDrag is an updated version of your draggable node with drag and drop handling. The CircleDragApp is just a JavaFX application test harness for the CircleDrag nodes.
CircleDrag.java
import javafx.event.Event;
import javafx.scene.SnapshotParameters;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import java.awt.Point;
import java.awt.MouseInfo;
public class CircleDrag extends Circle {
private final String name;
private int count = 0;
public CircleDrag(double centerX, double centerY, String name) {
super(centerX, centerY, 10);
this.name = name;
setOnDragDetected(e -> {
ClipboardContent content = new ClipboardContent();
content.putString(name);
Dragboard dragboard = startDragAndDrop(TransferMode.ANY);
dragboard.setContent(content);
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
dragboard.setDragView(snapshot(params, null));
dragboard.setDragViewOffsetX(dragboard.getDragView().getWidth() / 2);
dragboard.setDragViewOffsetY(dragboard.getDragView().getHeight() / 2);
setVisible(false);
e.consume();
logIt(e);
});
setOnDragEntered(this::logIt);
setOnDragDone(e ->
{
Point p = MouseInfo.getPointerInfo().getLocation();
relocate(
p.x - getScene().getWindow().getX() - getScene().getX() - getRadius(),
p.y - getScene().getWindow().getY() - getScene().getY() - getRadius()
);
setVisible(true);
logIt(e);
});
setOnDragDropped(e -> {
Dragboard db = e.getDragboard();
System.out.println("Dropped: " + db.getString() + " on " + name);
e.setDropCompleted(true);
e.consume();
logIt(e);
});
setOnDragOver(e -> {
if (e.getGestureSource() != this) {
e.acceptTransferModes(TransferMode.ANY);
logIt(e);
}
e.consume();
});
}
private void logIt(Event e) {
System.out.printf("%05d %s: %s\n", count++, name, e.getEventType().getName());
}
}
CircleDragApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class CircleDragApp extends Application {
private static final int W = 320;
private static final int H = 200;
private static final int R = 5;
private Random random = new Random(42);
public void start(Stage stage) throws Exception {
Pane pane = new Pane();
pane.setPrefSize(W, H);
for (int i = 0; i < 10; i++) {
CircleDrag circle = new CircleDrag(
random.nextInt(W - R) + R,
random.nextInt(H - R) + R,
i + ""
);
circle.setFill(Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
pane.getChildren().add(circle);
}
stage.setScene(new Scene(pane));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Parting Thoughts
Lorand's solution which does not make use of the drag event handlers looks pretty good in comparison to what I have and may have a less quirks. Study both and choose the solution which appears best for your situation.
My general recommendation is that if you are going to be doing data transfer handling, then the drag and drop APIs might be a good approach. If you are not doing data transfer handling, then sticking with plain mouse events might be the best approach.
I have written the below JavaFX program in which two rectangle nodes are in translate transition:
public class Test extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane borderPane = new BorderPane();
borderPane.setStyle("-fx-background-color: green;");
Rectangle rect1 = new Rectangle(20,20,50, 50);
rect1.setArcHeight(15);
rect1.setArcWidth(15);
rect1.setFill(Color.RED);
Rectangle rect2 = new Rectangle(20,20,30, 30);
rect2.setArcHeight(15);
rect2.setArcWidth(15);
rect2.setFill(Color.RED);
TranslateTransition translateTransition1 = new TranslateTransition(Duration.millis(2000), rect1);
translateTransition1.setFromX(0);
translateTransition1.setToX(300);
translateTransition1.setToY(300);
translateTransition1.setCycleCount(Timeline.INDEFINITE);
translateTransition1.setAutoReverse(true);
translateTransition1.play();
TranslateTransition translateTransition2 = new TranslateTransition(Duration.millis(2000), rect2);
translateTransition2.setFromX(300);
translateTransition2.setToX(0);
translateTransition2.setToY(300);
translateTransition2.setCycleCount(Timeline.INDEFINITE);
translateTransition2.setAutoReverse(true);
translateTransition2.play();
borderPane.getChildren().add(rect1);
borderPane.getChildren().add(rect2);
primaryStage.setScene(new Scene(borderPane, 500, 500));
primaryStage.show();
}
}
How can I implement collision detection of the two rectangle nodes which are in Translate Transition?
With rectangles it's pretty easy; just get their bounds in the parent and see if they intersect. The only drawback with this is it doesn't take into account the curved corners: you may need to compute that by hand if you want that level of accuracy. For non-rectangular shapes you can also just observe the bounds in parent properties, but you'd need to do the computation by hand to see if the shapes intersect.
ObservableBooleanValue colliding = Bindings.createBooleanBinding(new Callable<Boolean>() {
#Override
public Boolean call() throws Exception {
return rect1.getBoundsInParent().intersects(rect2.getBoundsInParent());
}
}, rect1.boundsInParentProperty(), rect2.boundsInParentProperty());
colliding.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs,
Boolean oldValue, Boolean newValue) {
if (newValue) {
System.out.println("Colliding");
} else {
System.out.println("Not colliding");
}
}
});
TranslateTransition isn't meant to support Collision Detection. It simply moves A to B without any regards to the state of anything but its node.
You would need a Transition mechanism that is aware of the other objects on the board.
The good news is that creating a Transition isn't too hard. You can create a class that inherits Transition and simply implement the interpolate() method.
From the JavaDoc:
Below is a simple example. It creates a small animation that updates
the text property of a Text node. It starts with an empty String and
adds gradually letter by letter until the full String was set when the
animation finishes.
final String content = "Lorem ipsum";
final Text text = new Text(10, 20, "");
final Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(2000));
}
protected void interpolate(double frac) {
final int length = content.length();
final int n = Math.round(length * (float) frac);
text.setText(content.substring(0, n));
}
};
The bad news is that having a successful collision detection mechanism is a bit harder. I'm really no expert on the subject, but I would probably have a ObservableList of Nodes that have collision, pass it to the Transition and on the interpolate method I would do a intersection check of the node that's moving against all the other nodes and leave it still if he cannot move.
If you want anything better than that, you'll probably want to look into a 2D Game Framework like Slick2D.
EDIT: Made a few simple alterations and went with a State based approach, code has been updated.
Well my approach is different that all the above ...
NOTE: I'm using 1.8 source
I created a Collidable interface:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.shape.Shape;
public interface Collidable{
public enum CollisionState{
WAITING,
TOUCHING;
}
ObjectProperty<CollisionState> state = new SimpleObjectProperty<>(CollisionState.WAITING);
public default ReadOnlyObjectProperty<CollisionState> collisionStateProperty(){return state;}
public default CollisionState getCollisionState(){return state.get();}
BooleanProperty collided = new SimpleBooleanProperty(false){{
addListener((ObservableValue<? extends Boolean> observable1, Boolean oldValue, Boolean touching) -> {
if(touching){
state.set(CollisionState.TOUCHING);
}else{
state.set(CollisionState.WAITING);
}
});
}};
public default boolean hasCollided(){return collided.get();}
public default BooleanProperty collidedProperty(){return collided;}
public default void checkCollision(Shape src, Shape other){
if(Shape.intersect(src, other).getBoundsInLocal().getWidth() > -1 && !getCollisionState().equals(CollisionState.TOUCHING)){
collided.set(true);
handleCollision(other);
}else if(Shape.intersect(src, other).getBoundsInLocal().getWidth() <= 0){
collided.set(false);
}
}
public void handleCollision(Shape other);
}
And a simple implementation:
import javafx.animation.Animation;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author Dub-Laptop
*/
public class CollisionTesting extends Application {
private TranslateTransition cAnim;
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Scene scene = new Scene(root);
primaryStage.setTitle("Collision Testing");
primaryStage.setScene(scene);
primaryStage.show();
Rectangle r = new Rectangle(100,50, Color.AQUA);
r.setLayoutX(10);
r.setLayoutY(200);
CollidableCircle c = new CollidableCircle(50, Color.GREEN);
c.setLayoutX(800);
c.setLayoutY(200);
/* can change this to anything you like
I used translateXProperty for simplicity
*/
c.translateXProperty().addListener((Observable observable) -> {
c.checkCollision(c, r);
});
root.getChildren().addAll(r, c);
TranslateTransition rAnim = new TranslateTransition();
rAnim.setToX(600);
rAnim.setAutoReverse(true);
rAnim.setCycleCount(Animation.INDEFINITE);
rAnim.setDuration(Duration.seconds(5));
rAnim.setNode(r);
cAnim = new TranslateTransition();
cAnim.setToX(-590);
cAnim.setAutoReverse(true);
cAnim.setCycleCount(Animation.INDEFINITE);
cAnim.setDuration(Duration.seconds(5));
cAnim.setNode(c);
rAnim.play();
cAnim.play();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private class CollidableCircle extends Circle implements Collidable{
public CollidableCircle(double radius, Paint fill) {
super(radius, fill);
new AnimationTimer(){
#Override
public void handle(long now) {
root.getChildren().filtered((Node n)->{
return !n.equals(CollidableCircle.this) && n instanceof Shape;
}).forEach(other ->{
checkCollision(CollidableCircle.this, (Shape)other);
});
}
}.start();
// I added this for local property changes to this node
collisionStateProperty().addListener((ObservableValue<? extends CollisionState> observable, CollisionState oldValue, CollisionState newValue) -> {
if(newValue.equals(CollisionState.TOUCHING)){
setScaleX(1.25);
setScaleY(1.25);
setFill(Color.GREENYELLOW);
cAnim.pause();
}else if(newValue.equals(CollisionState.WAITING)){
setScaleX(1.0);
setScaleY(1.0);
setFill(Color.GREEN);
cAnim.play();
}
});
}
#Override
public void handleCollision(Shape other) {
// handle updates that affect other node here
System.out.println("Collided with : " + other.getClass().getSimpleName());
}
}
}
IMHO rather than using Bounds for checking Shape collisions, use the boolean :
(Shape.intersect(s1,s2).getBoundsInLocal().getWidth() > -1)
This approach is more accurate for Shapes as it will check for non-null pixels within the Shape Bounds, rather than the normal rectangular Bounds.
Though if you really want to use Bounds, this should work also:
if(sourceShape.getBoundsInLocal().intersects(otherShape.getBoundsInParent()){
Shape intersect = Shape.intersect(sourceShape, otherShape);
if(intersect.getBoundsInLocal().getWidth > -1){
// handle code here
}
}
though, as you can see it's more verbose and virtually the same as my other method.
Hope this helps.
This is my first blackberry app and I am attempting to make a simple game for a touch screen device.
Its the typical image split into tiles and you have to unscramble them so the tiles are in the correct order and show the picture.
The intention is that the user will be able to "drag" a tile anywhere they want on the screen and they will swap places with the tile they drop it over.
I am using bitmapbuttons for my tiles
HorizontalFieldManager hfm = new HorizontalFieldManager();
add(hfm);
hfm.add(button1);
hfm.add(button2);
etc...
I have a class called Puzzle screen as follows:
class PuzzleScreen extends MainScreen implements FieldChangeListener {
I have added the following to try and detect the movement of the persons touch on the screen
protected boolean touchEvent(TouchEvent event) {
case TouchEvent.MOVE:
PuzzleTile fieldMove = (PuzzleTile) this.getLeafFieldWithFocus();
// Dialog.alert("MOVE");
int[] x_points;
int[] y_points;
int[] time_points;
int size = event.getMovePointsSize();
x_points = new int[size];
y_points = new int[size];
time_points = new int[size];
event.getMovePoints(1, x_points, y_points, time_points);
return true;
}
return false;
}
I need to find which image tile (bitmapbutton) is at the position of the upevent.
I have the coordinates (I think) above but not sure how to find which tile that relates to.
Bex
Something like this.
static class TestScreen extends MainScreen {
private static String down;
public TestScreen () {
HorizontalFieldManager hfm = new HorizontalFieldManager();
add(hfm);
hfm.add(new TestButton("bt1"));
hfm.add(new TestButton("bt2"));
hfm.add(new TestButton("bt3"));
hfm.add(new TestButton("bt4"));
hfm.add(new TestButton("bt5"));
hfm.add(new TestButton("bt6"));
}
public static void touchDown(TestButton tb) {
down = tb.getText();
System.out.println("DOWN in " + tb.getText());
}
public static void touchUp(TestButton tb) {
System.out.println("UP in " + tb.getText());
if(!down.equals(tb.getText()))
System.out.println("swap " + down + " and " + tb.getText());
down = "";
}
class TestButton extends LabelField {
public TestButton (String label) {
super(label);
}
protected boolean touchEvent(TouchEvent event) {
if(event.getEvent() == TouchEvent.UP)
TestScreen.touchUp(this);
else if(event.getEvent() == TouchEvent.DOWN)
TestScreen.touchDown(this);
return super.touchEvent(event);
}
}
}