AndEngine, attaching 2 of the same entity affecting collision - object

I am creating a game similar to Glider PRO. In the game, you control a paper airplane and you use air vents to keep you from falling on the floor. When the airplane collides with the air vent, (using collidesWith method) it pushes the airplane up. Now here is the problem, when I attach 2 air vents to the scene, only the first attached air vent works. The second one doesn't seem to collide with the airplane. I use the onManagedUpdate method in each air vent to check for the collision. This is my first time coding with AndEngine so any help on this will be greatly appreciated.
Here is the AirVent code:
public class AirVent extends Rectangle
{
private Sprite sprite;
private Glider glider;
public AirVent(float pX, float pY, float pWidth, float pHeight,
VertexBufferObjectManager pVertexBufferObjectManager,
ITextureRegion pITextureRegion)
{
super(pX, pY, pWidth, pHeight, pVertexBufferObjectManager);
sprite = new Sprite(0, 0, pITextureRegion, pVertexBufferObjectManager);
sprite.setSize(pWidth, pWidth / 2);
sprite.setPosition((pWidth / 2) - (sprite.getWidth() / 2), pHeight
- sprite.getHeight());
this.attachChild(sprite);
this.setAlpha(0);
}
public Sprite getSprite()
{
return sprite;
}
public void checkCollision()
{
if (glider.collidesWith(this))
{
glider.setGravity(glider.getConstantGravity() * -1f);
}
else
{
glider.setGravity(glider.getConstantGravity());
}
}
public void applyGliderInteraction(Glider glider)
{
this.glider = glider;
}
#Override
public void onManagedUpdate(float secondsElapsed)
{
this.checkCollision();
super.onManagedUpdate(secondsElapsed);
}
}
And the MainActivity code part:
#Override
protected Scene onCreateScene()
{
// TODO Auto-generated method stub
// 1 - Create new scene
final Scene scene = new Scene();
Sprite backgroundSprite = new Sprite(0, 0, this.mBackground, getVertexBufferObjectManager());
scene.attachChild(backgroundSprite);
roomChanger = new RoomChanger(0, 0, screenWidth, screenHeight, getVertexBufferObjectManager(), scene);
// 2 - Create the player
player1 = new Glider(339, 174, this.mPlayerGlider1, getVertexBufferObjectManager())
{
#Override
public void onManagedUpdate(float secondsElapsed)
{
if (player1.getLife() == 0)
{
// Execute your actions.
finish();
}
super.onManagedUpdate(secondsElapsed);
}
};
scene.attachChild(player1);
airVent = new AirVent(200, 100, 100, 400, getVertexBufferObjectManager(), this.mAirVent);
airVent.applyGliderInteraction(player1);
airVent2 = new AirVent(500, 100, 100, 400, getVertexBufferObjectManager(), this.mAirVent);
airVent2.applyGliderInteraction(player1);
scene.attachChild(airVent);
scene.attachChild(airVent2);
createControllers();
return scene;
}

I found the problem. The checkCollison() method works, but when I add a second AirVent, the if/else statements battle each other. Removing the else statement from the checkCollision() method fixed the issue but right now, that is not the way I want my game to behave.

Related

libgdx polygon sprite batch draw filled polygon

I need to draw polygon filled with color, but i don't know how to create my texture right, for batch to draw it
Here is my code
public class VisualComponent implements Component, Pool.Poolable {
public float[] vertices = new float[1];
public short[] triangles = new short[1];
#Override
public void reset() {
vertices = new float[1];
triangles = new short[1];
}
}
Here i am creating the entity which i want to be drawn
Entity source = engine.createEntity();
TransformComponent transformComponent = engine.createComponent(TransformComponent.class);
VisualComponent visualComponent = engine.createComponent(VisualComponent.class);
float redColorBits = Color.RED.toFloatBits();
visualComponent.vertices = new float[]{
0, 0, redColorBits,
10, 0, redColorBits,
10, 10, redColorBits,
0, 10, redColorBits
};
visualComponent.triangles = new short[]{
0, 1, 2,
0, 2, 3
};
source.add(transformComponent);
source.add(visualComponent);
engine.addEntity(source);
And here is the rendering system's processEntity method
#Override
protected void processEntity(Entity entity, float deltaTime) {
TransformComponent transformComponent = tcm.get(entity);
VisualComponent visualComponent = vcm.get(entity);
batch.draw(new Texture(1, 1, Pixmap.Format.RGBA8888),
visualComponent.vertices, 0, visualComponent.vertices.length,
visualComponent.triangles, 0, visualComponent.triangles.length);
}
Also I need a tip on how to correctly reuse my texture for not always recreating it. Thx
Okay I did it by inheriting from PolygonSpriteBatch and adding a 1x1 white pixel texture for draw function, here is the code which worked fine.
public class TexturedPolygonSpriteBatch extends PolygonSpriteBatch {
private Texture texture;
public TexturedPolygonSpriteBatch() {
super();
initTexture();
}
public void draw(float[] polygonVertices, int verticesOffset, int verticesCount,
short[] polygonTriangles,
int trianglesOffset, int trianglesCount) {
super.draw(texture, polygonVertices, verticesOffset, verticesCount,
polygonTriangles, trianglesOffset, trianglesCount);
}
#Override
public void dispose() {
texture.dispose();
super.dispose();
}
private void initTexture() {
Logger.log(getClass(), "Initializing 1x1 white texture");
Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
pixmap.setColor(Color.WHITE);
pixmap.fill();
texture = new Texture(pixmap);
pixmap.dispose();
}
}
Actually it was important to give a white color to the texture, because white color contains all the colors, if I set red color, only red color could be drawn there, think its some kind-a strange, but the woodoo worked.)

LibGdx: How to set a separate viewport for UI elements?

I'm trying to code a simple maze with a character that moves using a Joystick (touchpad). I used a Fit viewport to maintain the aspect ration of my maze while keeping it at the center of the screen;
However, I find that that the joystick is also added beside the maze. sample of what it looks like currently
I want the joystick to be of the far bottom-left of the screen. I tried doing this using a different viewport for UI elements (including the joystick), but then the whole maze just gets distorted.
Here is part of my BaseScreen class, an extension I made to reuse in other games as well:
public abstract class BaseScreen implements Screen, InputProcessor {
public BaseScreen(){
gameWorldHeight = 1024;
gameWorldWidth = 1024;
cam = new OrthographicCamera(gameWorldWidth, gameWorldHeight);
port = new FitViewport(cam.viewportWidth, cam.viewportHeight, cam);
port.apply();
cam.update();
mainStage = new Stage(port);
uiStage = new Stage(port);
uiTable = new Table();
uiTable.setFillParent(true);
uiStage.addActor(uiTable);
initialize();
}
public abstract void initialize();
public void render(float dt) {
mainStage.getCamera().update();
uiStage.getCamera().update();
uiStage.act(dt);
mainStage.act(dt);
update(dt);
mainStage.draw();
uiStage.draw();
}
public abstract void update(float dt);
public void resize(int width, int height) {
mainStage.getViewport().update(width,height, true);
uiStage.getViewport().update(width,height,false);
}
}
Here is also a part of my gameScreen class, where I actually code all the core mechanics of the game
public class gameScreen extends BaseScreen {
#Override
public void initialize() {
//..
uiTable.pad(10);
uiTable.add().expandX().expandY();
uiTable.row();
uiTable.add(ball.touchpad).left();
uiTable.add().expandX();
}
}
and in case you were wondering, here is what ball.touchpad is
public class Ball extends BaseActor {
public Touchpad touchpad;
public Touchpad.TouchpadStyle touchpadStyle;
public Skin touchpadSkin;
public Drawable touchBackground;
public Drawable touchKnob;
public Ball (float x, float y, Stage s) {
//..
//Create a touchpad skin
touchpadSkin = new Skin();
//Set background image
touchpadSkin.add("touchBackground", new Texture("touchBackground.png"));
//Set knob image
touchpadSkin.add("touchKnob", new Texture("touchKnob.png"));
//Create TouchPad Style
touchpadStyle = new Touchpad.TouchpadStyle();
//Create Drawable's from TouchPad skin
touchBackground = touchpadSkin.getDrawable("touchBackground");
touchKnob = touchpadSkin.getDrawable("touchKnob");
//Apply the Drawables to the TouchPad Style
touchpadStyle.background = touchBackground;
touchpadStyle.knob = touchKnob;
//Create new TouchPad with the created style
touchpad = new Touchpad(10, touchpadStyle);
//setBounds(x,y,width,height)
touchpad.setBounds(15, 15, 200, 200);
}
So how can i add this touchpad on the bottom left of the screen?

UI Component [JavaFX Group] loses its position while the window is resized ... occurs only after scrolling horizontally

Please go through the image below.
In the above image you can find that horizontal scrolling is not started.
Now visit the After Scroll image of the same contents...
Now in the second image you can see that horizontal scrolling is done ...
JFXPanel contents are scroll horizontally... Which was perfect...
Now the third image will describe the problem....
It is liitle bit stretched to see as it is maximized...
You can see that the JFXPanel contents have changed their original position...
Moreover the contents must start with X_DisplaceMent = 0.0 [X-Cordinate], which was done automatically in the first two images...
All the contents are nodes like [Rectangle,Line etc.. ], after that all are placed in Group node...
And this Group node is set in the ScrollPane through
js.setContent(Group node);
Each component is placed with given x,y cordiante value .. then how did this happen while doing the maximized ?
Please help me to find the root cause ...
Thanks in advance...
Here are some facts that cause the problem.
- Start Position of Scene : 0.0
- Start Position of Group in Scene : 49.5
- Width of the root : 364.5
- Start Position of Scene : 0.0
- Start Position of Group in Scene : 63.5
- Width of the root : 364.5
- Start Position of Scene : 0.0
- Start Position of Group in Scene : 83.5
- Width of the root : 364.5
Whenever we drag the window horizontally Group is moving in the scene... That should not happen... how to avoid this ...
Ok... Here is the MCVE.....
There is a Frame. which contain SplitPane having vertical split.
The SplitPane will show the contents of two JFxPanels.
Both fxpanels are having rectangle on same x cordinate but Y cordinate is different.
And both the fxPanels are horizontal scroll sync. Not bi-directional. When you scroll lower panel horizonatally, the upper panel will get scrolled due to horizontal sync.
Here is the code for fxPanel 1...
public class FxPanel1 extends JFXPanel
{
private ScrollPane scroll ;
public ScrollPane getJs() {
return scroll;
}
public void setJs(ScrollPane js) {
this.scroll = js;
}
private boolean initFX(JFXPanel fxPanel) {
Scene scene = createScene();
fxPanel.setScene(scene);
return true;
//craneAssignmentChartView.setFxPanel(fxPanel);
}
private Scene createScene() {
Group root = new Group();
Rectangle rect = new Rectangle(10.0, 20.0, 800, 40);
rect.setFill(javafx.scene.paint.Color.TRANSPARENT);
rect.setStroke(javafx.scene.paint.Color.RED);
AnchorPane anchor = new AnchorPane();
anchor.getChildren().add(rect);
GridPane grid = new GridPane();
grid.setHgap(0);
grid.setVgap(0);
grid.setPadding(new Insets(0, 0, 0, 0));
grid.add(anchor, 1, 0);
root.getChildren().add(grid);
ScrollPane scroll = new ScrollPane();
scroll.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
scroll.setContent(root);
setJs(scroll);
return new Scene(scroll, javafx.scene.paint.Color.WHITE);
}
private void createUI(final JFXPanel fxPanel)
{
Platform.runLater(new Runnable() {
#Override
public void run()
{
initFX(fxPanel);
}
});
}
public FxPanel1( JFXPanel fxPanel)
{
createUI(fxPanel);
}
}
Now the code for second fxPanel looks like ...
public class FxPanel2 extends JFXPanel
{
private ScrollPane scroll ;
public ScrollPane getJs() {
return scroll;
}
public void setJs(ScrollPane js) {
this.scroll = js;
}
private boolean initFX(JFXPanel fxPanel) {
Scene scene = createScene();
fxPanel.setScene(scene);
return true;
//craneAssignmentChartView.setFxPanel(fxPanel);
}
private Scene createScene() {
Group root = new Group();
Rectangle rect = new Rectangle(10.0, 180.0, 800, 40);
rect.setFill(javafx.scene.paint.Color.TRANSPARENT);
rect.setStroke(javafx.scene.paint.Color.RED);
AnchorPane anchor = new AnchorPane();
anchor.getChildren().add(rect);
GridPane grid = new GridPane();
grid.setHgap(0);
grid.setVgap(0);
grid.setPadding(new Insets(0, 0, 0, 0));
grid.add(anchor, 1, 0);
root.getChildren().add(grid);
ScrollPane scroll = new ScrollPane();
scroll.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setVbarPolicy(ScrollBarPolicy.NEVER);
scroll.setContent(root);
setJs(scroll);
return new Scene(scroll, javafx.scene.paint.Color.WHITE);
}
private void createUI(final JFXPanel fxPanel)
{
Platform.runLater(new Runnable() {
#Override
public void run()
{
initFX(fxPanel);
}
});
}
public FxPanel2( JFXPanel fxPanel)
{
createUI(fxPanel);
}
}
The main class looks like ....
public class DemoToCheckUIAlignment extends JFrame
{
public static void main(String[] args)
{
final DemoToCheckUIAlignment demo = new DemoToCheckUIAlignment();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFXPanel panel1 = new JFXPanel();
FxPanel1 fxObj1 = new FxPanel1(panel1);
JFXPanel panel2 = new JFXPanel();
FxPanel2 fxObj2 = new FxPanel2(panel2);
DemoToCheckUIAlignment frame = new DemoToCheckUIAlignment();
frame.setSize(800, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JSplitPane chartSplitPane = new JSplitPane();
chartSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
chartSplitPane.setDividerLocation(200);
chartSplitPane.setDividerSize(2);
chartSplitPane.setTopComponent(panel1);
chartSplitPane.setBottomComponent(panel2);
demo.provideScrollSyncBetweenFXPanels(fxObj1.getJs(), fxObj2.getJs());
frame.getContentPane().add(chartSplitPane);
//frame.getContentPane().add(panel2);
frame.setVisible(true);
}
});
}
public static void provideScrollSyncBetweenFXPanels(final ScrollPane upperSP, final ScrollPane lowerSP)
{
lowerSP.hvalueProperty().addListener(new ChangeListener<Number>()
{
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val)
{
upperSP.hvalueProperty().set(new_val.doubleValue());
}
});
}
}
Now to check the problem follow the simple steps...
Ofcorse run the program...
Scroll the bottom Panel ....that is FxPanel2...
And maximized the window .... The x - position for the inner contents is changed now...
which does not happen with Swing....
Here are the screen shots where the problem reproduce for the attached MCVE....Please go through the images....
You may use setFitToWidth of the ScrollPane Object to match a particular dimension. For more details you may refer to the link http://docs.oracle.com/javafx/2/ui_controls/scrollpane.htm at down the page, Resizing Components in the Scroll Pane, you may find more on the solution

How can I make a TextArea stretch to fill the content, expanding the parent in the process?

So I have a TextArea and as the user pastes paragraphs into it, or just writes in it, I want it to expand vertically to reveal all the available text. I.e. not to use a scrollbar in the text field itself... much like what happens on many web pages. Many users, myself included, don't like to be forced to edit in a small window. Exactly how Facebook status updates box works.
I've tried
myTextArea.autoSize()
wrapped in an
myTextArea.textProperty().addListener(new ChangeListener()....);
but that doesn't work. I think it's happy autosizing to its current size.
The left, right & top anchors are set to it's parent AnchorPane. I've tried it with the bottom attached and not attached. Ideally I'd like to grow the anchor pane as the textarea grows.
I don't mind reading the TextProperty and calculating a trigger size which I set myself... but this seems a hacky approach IF there is already a best practise. The number of properties and sub objects of javafx is sufficiently daunting that it seems like a good point to ask the question here, rather than trying to figure out how many pixels the font/paragraphs etc are taking up.
Update:
So I thought maybe I was overthinking it, and all I needed to do was to switch the scrollbars off and the rest would happen. Alas, looking for available fields and methods for "scroll", "vertical", "vbar" comes up with nothing I can use. ScrollTopProperty looks like it's for something else.
The problem; the height of textArea is wanted to be grown or shrunk while its text is changing by either user's typing or copy-pasting. Here is another approach:
public class TextAreaDemo extends Application {
private Text textHolder = new Text();
private double oldHeight = 0;
#Override
public void start(Stage primaryStage) {
final TextArea textArea = new TextArea();
textArea.setPrefSize(200, 40);
textArea.setWrapText(true);
textHolder.textProperty().bind(textArea.textProperty());
textHolder.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
if (oldHeight != newValue.getHeight()) {
System.out.println("newValue = " + newValue.getHeight());
oldHeight = newValue.getHeight();
textArea.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 20); // +20 is for paddings
}
}
});
Group root = new Group(textArea);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
// See the explanation below of the following line.
// textHolder.setWrappingWidth(textArea.getWidth() - 10); // -10 for left-right padding. Exact value can be obtained from caspian.css
}
public static void main(String[] args) {
launch(args);
}
}
But it has a drawback; the textarea's height is changing only if there are line breaks (ie Enter keys) between multiple lines, if the user types long enough the text gets wrapped to multiple line but the height is not changing.
To workaround this drawback I added this line
textHolder.setWrappingWidth(textArea.getWidth() - 10);
after primaryStage.show();. It works well for long typings where user does not linebreaks. However this generates another problem. This problem occurs when the user is deleting the text by hitting "backspace". The problem occurs exactly when the textHolder height is changed and where the textArea's height is set to new value. IMO it maybe a bug, didn't observe deeper.
In both case the copy-pasting is handling properly.
Awaiting a better, i use this hacky solution.
lookup the vertical scrollbar of the textarea.
make it transparent
listen to its visible property
when the scrollbar become visible i add a row to the textarea.
The code:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class GrowGrowTextArea extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
AnchorPane root = new AnchorPane();
root.setStyle("-fx-padding:20;-fx-background-color:dodgerblue;");
final TextArea textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 10.0);
AnchorPane.setLeftAnchor(textArea, 10.0);
AnchorPane.setRightAnchor(textArea, 10.0);
root.getChildren().add(textArea);
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
ScrollBar scrollBar = lookupVerticalScrollBar(textArea);
scrollBar.setOpacity(0.0);
scrollBar.visibleProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> source,
Boolean wasVisible,
Boolean isVisible) {
if (isVisible) {
textArea.setPrefRowCount(textArea.getPrefRowCount() + 1);
textArea.requestLayout();
}
}
});
}
private ScrollBar lookupVerticalScrollBar(Node node) {
if (node instanceof ScrollBar && ((ScrollBar)node).getOrientation() == Orientation.VERTICAL) {
return (ScrollBar) node;
}
if (node instanceof Parent) {
ObservableList<Node> children = ((Parent) node).getChildrenUnmodifiable();
for (Node child : children) {
ScrollBar scrollBar = lookupVerticalScrollBar(child);
if (scrollBar != null) {
return scrollBar;
}
}
}
return null;
}
}
I had a similar problem with creating expanding TextArea. I was creating TextArea that looks like TextField and expand vertically every time when there is no more space in line.
I have tested all solutions that I could find on this topic on stack and other sources available. I found few good solutions but neither was good enough.
After many hours of fighting, I figured out this approach.
I extended TextArea class, override layoutChildren() method and add a listener on text height.
#Override
protected void layoutChildren() {
super.layoutChildren();
setWrapText(true);
addListenerToTextHeight();
}
private void addListenerToTextHeight() {
ScrollPane scrollPane = (ScrollPane) lookup(".scroll-pane");
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
StackPane viewport = (StackPane) scrollPane.lookup(".viewport");
Region content = (Region) viewport.lookup(".content");
Text text = (Text) content.lookup(".text");
text.textProperty().addListener(textHeightListener(text));
}
private InvalidationListener textHeightListener(Text text) {
return (property) -> {
// + 1 for little margin
double textHeight = text.getBoundsInLocal().getHeight() + 1;
//To prevent that our TextArena will be smaller than our TextField
//I used DEFAULT_HEIGHT = 18.0
if (textHeight < DEFAULT_HEIGHT) {
textHeight = DEFAULT_HEIGHT;
}
setMinHeight(textHeight);
setPrefHeight(textHeight);
setMaxHeight(textHeight);
};
}
I used some of the code found in the previous answers.
The growTextAreaIfNecessary method will increase the height of textArea until the scrollbar is not visible (limited to 20 lines in this example).
The problem with this approach is that the window needs to be redrawn several times until the perfect height is found.
private ScrollBar lookupVerticalScrollBar(Node node) {
if (node instanceof ScrollBar && ((ScrollBar) node).getOrientation() == Orientation.VERTICAL) {
return (ScrollBar) node;
}
if (node instanceof Parent) {
ObservableList<Node> children = ((Parent) node).getChildrenUnmodifiable();
for (Node child : children) {
ScrollBar scrollBar = lookupVerticalScrollBar(child);
if (scrollBar != null) {
return scrollBar;
}
}
}
return null;
}
private void growTextAreaIfNecessary(TextArea textArea) {
Platform.runLater(() -> {
ScrollBar lookupVerticalScrollBar = lookupVerticalScrollBar(textArea);
int prefRowCount = textArea.getPrefRowCount();
if (lookupVerticalScrollBar.isVisible() && prefRowCount < 20) {
textArea.setPrefRowCount(prefRowCount + 1);
System.out.println("increasing height to: " + (prefRowCount + 1));
growTextAreaIfNecessary(textArea);
}
});
}
I have tried many hacks, most of them had jitters while typing, this to me was the perfect result:
textArea.textProperty().addListener((obs,old,niu)->{
Text t = new Text(old+niu);
t.setFont(textArea.getFont());
StackPane pane = new StackPane(t);
pane.layout();
double height = t.getLayoutBounds().getHeight();
double padding = 20 ;
textArea.setMinHeight(height+padding);
});

Checking Collision of Shapes with JavaFX

I am trying to do some collision detection. For this test I am using simple rectangular Shape, and checking their Bound, to figure if they are colliding. Although the detection does not work as expected. I have tried using different ways to move the object(relocate, setLayoutX,Y) and also different bound checks (boundsInLocal,boundsInParrent etc) but I still cannot get this to work. As you can see the detection works only for one object, even when you have three objects only one detects collision. This is some working code demonstrating the problem:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
public class CollisionTester extends Application {
private ArrayList<Rectangle> rectangleArrayList;
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
primaryStage.setTitle("The test");
Group root = new Group();
Scene scene = new Scene(root, 400, 400);
rectangleArrayList = new ArrayList<Rectangle>();
rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.GREEN));
rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.RED));
rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.CYAN));
for(Rectangle block : rectangleArrayList){
setDragListeners(block);
}
root.getChildren().addAll(rectangleArrayList);
primaryStage.setScene(scene);
primaryStage.show();
}
public void setDragListeners(final Rectangle block) {
final Delta dragDelta = new Delta();
block.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = block.getTranslateX() - mouseEvent.getSceneX();
dragDelta.y = block.getTranslateY() - mouseEvent.getSceneY();
block.setCursor(Cursor.NONE);
}
});
block.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
block.setCursor(Cursor.HAND);
}
});
block.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
block.setTranslateX(mouseEvent.getSceneX() + dragDelta.x);
block.setTranslateY(mouseEvent.getSceneY() + dragDelta.y);
checkBounds(block);
}
});
}
private void checkBounds(Rectangle block) {
for (Rectangle static_bloc : rectangleArrayList)
if (static_bloc != block) {
if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {
block.setFill(Color.BLUE); //collision
} else {
block.setFill(Color.GREEN); //no collision
}
} else {
block.setFill(Color.GREEN); //no collision -same block
}
}
class Delta {
double x, y;
}
}
Looks like you have a slight logic error in your checkBounds routine - you are correctly detecting collisions (based on bounds) but are overwriting the fill of your block when you perform subsequent collision checks in the same routine.
Try something like this - it adds a flag so that the routine does not "forget" that a collision was detected:
private void checkBounds(Shape block) {
boolean collisionDetected = false;
for (Shape static_bloc : nodes) {
if (static_bloc != block) {
static_bloc.setFill(Color.GREEN);
if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {
collisionDetected = true;
}
}
}
if (collisionDetected) {
block.setFill(Color.BLUE);
} else {
block.setFill(Color.GREEN);
}
}
Note that the check you are doing (based on bounds in parent) will report intersections of the rectangle enclosing the visible bounds of nodes within the same parent group.
Alternate Implementation
In case you need it, I updated your original sample so that it is able to check based on the visual shape of the Node rather than the bounding box of the visual shape. This lets you to accurately detect collisions for non-rectangular shapes such as Circles. The key for this is the Shape.intersects(shape1, shape2) method.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.ArrayList;
import javafx.scene.shape.*;
public class CircleCollisionTester extends Application {
private ArrayList<Shape> nodes;
public static void main(String[] args) { launch(args); }
#Override public void start(Stage primaryStage) {
primaryStage.setTitle("Drag circles around to see collisions");
Group root = new Group();
Scene scene = new Scene(root, 400, 400);
nodes = new ArrayList<>();
nodes.add(new Circle(15, 15, 30));
nodes.add(new Circle(90, 60, 30));
nodes.add(new Circle(40, 200, 30));
for (Shape block : nodes) {
setDragListeners(block);
}
root.getChildren().addAll(nodes);
checkShapeIntersection(nodes.get(nodes.size() - 1));
primaryStage.setScene(scene);
primaryStage.show();
}
public void setDragListeners(final Shape block) {
final Delta dragDelta = new Delta();
block.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = block.getLayoutX() - mouseEvent.getSceneX();
dragDelta.y = block.getLayoutY() - mouseEvent.getSceneY();
block.setCursor(Cursor.NONE);
}
});
block.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
block.setCursor(Cursor.HAND);
}
});
block.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
block.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
block.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
checkShapeIntersection(block);
}
});
}
private void checkShapeIntersection(Shape block) {
boolean collisionDetected = false;
for (Shape static_bloc : nodes) {
if (static_bloc != block) {
static_bloc.setFill(Color.GREEN);
Shape intersect = Shape.intersect(block, static_bloc);
if (intersect.getBoundsInLocal().getWidth() != -1) {
collisionDetected = true;
}
}
}
if (collisionDetected) {
block.setFill(Color.BLUE);
} else {
block.setFill(Color.GREEN);
}
}
class Delta { double x, y; }
}
Sample program output. In the sample the circles have been dragged around and the user is currently dragging a circle which has been marked as colliding with another circle (by painting it blue) - for demonstration purposes only the circle currently being dragged has it's collision color marked.
Comments based on additional questions
The link I posted to an intersection demo application in a prior comment was to illustrate the use of various bounds types rather than as a specific type of collision detection sample. For your use case, you don't need the additional complexity of the change listener and checking on various different kinds of bounds types - just settling on one type will be enough. Most collision detection is only going to be interested in intersection of visual bounds rather than other JavaFX bounds types such as the layout bounds or local bounds of a node. So you can either:
Check for intersection of getBoundsInParent (as you did in your original question) which works on the smallest rectangular box which will encompass the visual extremities of the node OR
Use the Shape.intersect(shape1, shape2) routine if you need to check based on the visual shape of the Node rather than the bounding box of the visual shape.
Should I be using setLayoutX or translateX for the rectangle
The layoutX and layoutY properties are intended for positioning or laying out nodes. The translateX and translateY properties are intended for temporary changes to the visual location of a node (for example when the node is undergoing an animation). For your example, though either property will work, it is perhaps better form to use the layout properties than the translate ones, that way if you did want to run something like a TranslateTransition on the nodes, it will be more obvious what the start and end translate values should be as those values will be relative to the current layout position of the node rather than the position in the parent group.
Another way you could use these layout and translate co-ordinates in tandem in your sample is if you had something like an ESC to cancel during the course of a drag operation. You could set layoutX,Y to the initial location of your node, start a drag operation which sets translateX,Y values and if the user presses ESC, set translateX,Y back to 0 to cancel the drag operation or if the user releases the mouse set layoutX,Y to layoutX,Y+translateX,Y and set translateX,Y back to 0. The idea is that the translation is values are used for a temporary modification of the visual co-ordinates of the node from it's original layout position.
will the intersect work even though the circles are animated? I mean without dragging the circle by mouse, what will happen if I made them to move around randomly. Will the colour change in this case also?
To do this, just change where the collision detection function is called and the collision handler invoked. Rather than checking for intersections based upon a mouse drag event (like the example above), instead check for collisions within a change listener on each node's boundsInParentProperty().
block.boundsInParentProperty().addListener((observable, oldValue, newValue) ->
checkShapeIntersection(block)
);
Note: if you have lots of shapes being animated, then checking for collisions once per frame within a game loop will be more efficient than running a collision check whenever any node moves (as is done in the boundsInParentProperty change listener above).
Additional info for handling input on non-rectangular shapes
For input detection not collision detection, so not directly related to your question, look at the node.pickOnBounds setting if you need mouse or touch interaction with a non-rectangular node.

Resources